diff options
833 files changed, 35705 insertions, 11192 deletions
diff --git a/Android.bp b/Android.bp index 908274de793b..b3faef1050f6 100644 --- a/Android.bp +++ b/Android.bp @@ -221,11 +221,11 @@ filegroup { ":framework-sax-sources", ":framework-telecomm-sources", ":framework-telephony-common-sources", - ":framework-telephony-sources", ":framework-wifi-annotations", ":framework-wifi-non-updatable-sources", ":PacProcessor-aidl-sources", ":ProxyHandler-aidl-sources", + ":net-utils-framework-common-srcs", // AIDL from frameworks/base/native/ ":platform-compat-native-aidl", @@ -255,6 +255,9 @@ filegroup { // etc. ":framework-javastream-protos", ":framework-statslog-gen", + + // telephony annotations + ":framework-telephony-annotations", ], } @@ -267,7 +270,9 @@ filegroup { ":framework-tethering-srcs", ":updatable-media-srcs", ":framework-mediaprovider-sources", + ":framework-permission-sources", ":framework-wifi-updatable-sources", + ":framework-telephony-sources", ":ike-srcs", ] } @@ -301,7 +306,6 @@ java_defaults { "rs/java", "sax/java", "telecomm/java", - "telephony/java", "wifi/java", ], }, @@ -381,6 +385,7 @@ java_defaults { "updatable_media_stubs", "framework_mediaprovider_stubs", "framework-tethering", + "framework-telephony-stubs", ], jarjar_rules: ":framework-jarjar-rules", @@ -408,6 +413,7 @@ filegroup { filegroup { name: "libincident_aidl", srcs: [ + "core/java/android/os/IIncidentDumpCallback.aidl", "core/java/android/os/IIncidentManager.aidl", "core/java/android/os/IIncidentReportStatusListener.aidl", ], @@ -436,8 +442,9 @@ java_library { srcs: [":framework-non-updatable-sources"], libs: [ "framework-appsearch-stubs", - // TODO(b/146167933): Use framework-statsd-stubs - "framework-statsd", + "framework-sdkextensions-stubs-systemapi", + "framework-statsd", // TODO(b/146167933): Use framework-statsd-stubs + "framework-permission-stubs", "framework-wifi-stubs", "ike-stubs", ], @@ -463,7 +470,9 @@ java_library { "//frameworks/base/apex/appsearch/framework", "//frameworks/base/apex/blobstore/framework", "//frameworks/base/apex/jobscheduler/framework", + "//frameworks/base/apex/permission/framework", "//frameworks/base/apex/statsd/service", + "//frameworks/base/telephony", "//frameworks/base/wifi", "//frameworks/opt/net/wifi/service", ], @@ -486,7 +495,8 @@ java_library { "framework-minus-apex", "updatable_media_stubs", "framework_mediaprovider_stubs", - "framework-appsearch-stubs", + "framework-appsearch", // TODO(b/146218515): should be framework-appsearch-stubs + "framework-permission-stubs", "framework-sdkextensions-stubs-systemapi", // TODO(b/146167933): Use framework-statsd-stubs instead. "framework-statsd", @@ -495,6 +505,9 @@ java_library { "ike-stubs", // TODO(b/147200698): should be the stub of framework-tethering "framework-tethering", + // TODO (b/147688669) should be framework-telephony-stubs + "framework-telephony", + // TODO(jiyong): add stubs for APEXes here ], sdk_version: "core_platform", apex_available: ["//apex_available:platform"], @@ -508,7 +521,8 @@ java_library { static_libs: [ "exoplayer2-core", "android.hardware.wifi-V1.0-java-constants", - ], + ], + libs: ["icing-java-proto-lite"], apex_available: ["//apex_available:platform"], } @@ -635,7 +649,11 @@ filegroup { name: "framework-ike-shared-srcs", visibility: ["//frameworks/opt/net/ike"], srcs: [ + "core/java/android/annotation/StringDef.java", "core/java/android/net/annotations/PolicyDirection.java", + "core/java/com/android/internal/util/IState.java", + "core/java/com/android/internal/util/State.java", + "core/java/com/android/internal/util/StateMachine.java", "telephony/java/android/telephony/Annotation.java", ], } @@ -1119,6 +1137,7 @@ filegroup { "core/java/android/util/TimeUtils.java", "core/java/com/android/internal/os/SomeArgs.java", "core/java/com/android/internal/util/AsyncChannel.java", + "core/java/com/android/internal/util/AsyncService.java", "core/java/com/android/internal/util/BitwiseInputStream.java", "core/java/com/android/internal/util/FastXmlSerializer.java", "core/java/com/android/internal/util/HexDump.java", @@ -1214,3 +1233,82 @@ build = [ "StubLibraries.bp", "ApiDocs.bp", ] + +// TODO(b/147699819): move to frameworks/base/telephony/ folder +droidstubs { + name: "framework-telephony-stubs-srcs", + srcs: [ + ":framework-telephony-sources", + ":framework_native_aidl", + ":framework-javastream-protos", + ], + aidl: { + local_include_dirs: [ + "core/java", + "telecomm/java" + ], + }, + libs: [ + "framework-annotations-lib", + "android.hardware.radio-V1.5-java", + ], + defaults: ["framework-module-stubs-defaults-systemapi"], + filter_packages: ["android.telephony"], + sdk_version: "system_current", +} + +java_library { + name: "framework-telephony-stubs", + srcs: [":framework-telephony-stubs-srcs"], + // TODO(b/147699819): move public aidls to a separate folder and potentially remove + // below aidl exports. + aidl: { + export_include_dirs: ["telephony/java"], + }, + sdk_version: "system_current", +} + +java_library { + name: "framework-telephony", + srcs: [ + ":framework-telephony-sources", + ], + // TODO: change to framework-system-stub to build against system APIs. + libs: [ + "framework-minus-apex", + "unsupportedappusage", + ], + static_libs: [ + "libphonenumber-platform", + "app-compat-annotations", + ], + sdk_version: "core_platform", + aidl: { + export_include_dirs: ["telephony/java"], + include_dirs: [ + "frameworks/native/aidl/binder", + "frameworks/native/aidl/gui", + ] + }, + jarjar_rules: ":telephony-framework-jarjar-rules", + dxflags: [ + "--core-library", + "--multi-dex", + ], + // This is to break the dependency from boot jars. + dex_preopt: { + enabled: false, + }, + installable: true, +} + +filegroup { + // TODO (b/147690217): move to frameworks/base/telephony/common. + name: "framework-telephony-annotations", + srcs: ["telephony/java/android/telephony/Annotation.java"], +} + +filegroup { + name: "telephony-framework-jarjar-rules", + srcs: ["telephony/framework-telephony-jarjar-rules.txt"], +} diff --git a/ApiDocs.bp b/ApiDocs.bp index e373db66925f..c40004cf8e5c 100644 --- a/ApiDocs.bp +++ b/ApiDocs.bp @@ -121,8 +121,10 @@ framework_docs_only_args = " -android -manifest $(location core/res/AndroidManif doc_defaults { name: "framework-docs-default", - libs: framework_docs_only_libs + - ["stub-annotations"], + libs: framework_docs_only_libs + [ + "stub-annotations", + "unsupportedappusage", + ], html_dirs: [ "docs/html", ], diff --git a/StubLibraries.bp b/StubLibraries.bp index baa3c615039d..cdc0d322eedb 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -51,6 +51,10 @@ stubs_defaults { ":core_public_api_files", ":ike-api-srcs", ], + // TODO(b/147699819): remove below aidl includes. + aidl: { + local_include_dirs: ["telephony/java"], + }, libs: ["framework-internal-utils"], installable: false, annotations_enabled: true, diff --git a/apex/appsearch/framework/Android.bp b/apex/appsearch/framework/Android.bp index 60cc3bec0a9d..2e8811506e0b 100644 --- a/apex/appsearch/framework/Android.bp +++ b/apex/appsearch/framework/Android.bp @@ -13,27 +13,49 @@ // limitations under the License. filegroup { - name: "framework-appsearch-sources", - srcs: [ - "java/**/*.java", - "java/**/*.aidl", - ], - path: "java", + name: "framework-appsearch-sources", + srcs: [ + "java/**/*.java", + "java/**/*.aidl", + ], + path: "java", } java_library { - name: "framework-appsearch", - installable: true, - sdk_version: "core_platform", // TODO(b/146218515) should be core_current - srcs: [":framework-appsearch-sources"], - hostdex: true, // for hiddenapi check - libs: [ - "framework-minus-apex", // TODO(b/146218515) should be framework-system-stubs - ], - visibility: ["//frameworks/base/apex/appsearch:__subpackages__"], - apex_available: ["com.android.appsearch"], + name: "framework-appsearch", + installable: true, + sdk_version: "core_platform", // TODO(b/146218515) should be core_current + srcs: [":framework-appsearch-sources"], + hostdex: true, // for hiddenapi check + libs: [ + "framework-minus-apex", // TODO(b/146218515) should be framework-system-stubs + ], + static_libs: [ + "icing-java-proto-lite", + ], + visibility: [ + "//frameworks/base/apex/appsearch:__subpackages__", + // TODO(b/146218515) remove this when framework is built with the stub of appsearch + "//frameworks/base", + ], + apex_available: ["com.android.appsearch"], } +metalava_appsearch_docs_args = + "--hide-package com.android.server " + + "--error UnhiddenSystemApi " + + "--hide RequiresPermission " + + "--hide MissingPermission " + + "--hide BroadcastBehavior " + + "--hide HiddenSuperclass " + + "--hide DeprecationMismatch " + + "--hide UnavailableSymbol " + + "--hide SdkConstant " + + "--hide HiddenTypeParameter " + + "--hide Todo --hide Typo " + + "--hide HiddenTypedefConstant " + + "--show-annotation android.annotation.SystemApi " + droidstubs { name: "framework-appsearch-stubs-srcs", srcs: [ @@ -43,8 +65,9 @@ droidstubs { aidl: { include_dirs: ["frameworks/base/core/java"], }, - defaults: ["framework-module-stubs-defaults-systemapi"], - sdk_version: "system_current", + args: metalava_appsearch_docs_args, + sdk_version: "core_current", + libs: ["android_system_stubs_current"], } java_library { @@ -55,6 +78,7 @@ java_library { "java", ], }, - sdk_version: "system_current", + sdk_version: "core_current", + libs: ["android_system_stubs_current"], installable: false, } diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearch.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearch.java new file mode 100644 index 000000000000..e779b69750c2 --- /dev/null +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearch.java @@ -0,0 +1,762 @@ +/* + * 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.app.appsearch; + +import android.annotation.CurrentTimeSecondsLong; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Bundle; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; + +import com.google.android.icing.proto.DocumentProto; +import com.google.android.icing.proto.PropertyProto; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +/** + * Collection of all AppSearch Document Types. + * + * @hide + */ +// TODO(b/143789408) Spilt this class to make all subclasses to their own file. +public final class AppSearch { + + private AppSearch() {} + /** + * Represents a document unit. + * + * <p>Documents are constructed via {@link Document.Builder}. + * + * @hide + */ + // TODO(b/143789408) set TTL for document in mProtoBuilder + // TODO(b/144518768) add visibility field if the stakeholders are comfortable with a no-op + // opt-in for this release. + public static class Document { + private static final String TAG = "AppSearch.Document"; + + /** + * The maximum number of elements in a repeatable field. Will reject the request if exceed + * this limit. + */ + private static final int MAX_REPEATED_PROPERTY_LENGTH = 100; + + /** + * The maximum {@link String#length} of a {@link String} field. Will reject the request if + * {@link String}s longer than this. + */ + private static final int MAX_STRING_LENGTH = 20_000; + + /** + * Contains {@link Document} basic information (uri, schemaType etc) and properties ordered + * by keys. + */ + @NonNull + private final DocumentProto mProto; + + /** Contains all properties in {@link #mProto} to support get properties via keys. */ + @NonNull + private final Bundle mPropertyBundle; + + /** + * Create a new {@link Document}. + * @param proto Contains {@link Document} basic information (uri, schemaType etc) and + * properties ordered by keys. + * @param propertyBundle Contains all properties in {@link #mProto} to support get + * properties via keys. + */ + private Document(@NonNull DocumentProto proto, @NonNull Bundle propertyBundle) { + this.mProto = proto; + this.mPropertyBundle = propertyBundle; + } + + /** + * Create a new {@link Document} from an existing instance. + * + * <p>This method should be only used by constructor of a subclass. + */ + // TODO(b/143789408) add constructor take DocumentProto to create a document. + protected Document(@NonNull Document document) { + this(document.mProto, document.mPropertyBundle); + } + + /** + * Creates a new {@link Document.Builder}. + * + * @param uri The uri of {@link Document}. + * @param schemaType The schema type of the {@link Document}. The passed-in + * {@code schemaType} must be defined using {@link AppSearchManager#setSchema} prior to + * inserting a document of this {@code schemaType} into the AppSearch index using + * {@link AppSearchManager#put}. Otherwise, the document will be rejected by + * {@link AppSearchManager#put}. + * @hide + */ + @NonNull + public static Builder newBuilder(@NonNull String uri, @NonNull String schemaType) { + return new Builder(uri, schemaType); + } + + /** + * Get the {@link DocumentProto} of the {@link Document}. + * + * <p>The {@link DocumentProto} contains {@link Document}'s basic information and all + * properties ordered by keys. + * @hide + */ + @NonNull + @VisibleForTesting + public DocumentProto getProto() { + return mProto; + } + + /** + * Get the uri of the {@link Document}. + * + * @hide + */ + @NonNull + public String getUri() { + return mProto.getUri(); + } + + /** + * Get the schema type of the {@link Document}. + * @hide + */ + @NonNull + public String getSchemaType() { + return mProto.getSchema(); + } + + /** + * Get the creation timestamp in seconds of the {@link Document}. + * + * @hide + */ + // TODO(b/143789408) Change seconds to millis with Icing library. + @CurrentTimeSecondsLong + public long getCreationTimestampSecs() { + return mProto.getCreationTimestampSecs(); + } + + /** + * Returns the score of the {@link Document}. + * + * <p>The score is a query-independent measure of the document's quality, relative to other + * {@link Document}s of the same type. + * + * <p>The default value is 0. + * + * @hide + */ + public int getScore() { + return mProto.getScore(); + } + + /** + * Retrieve a {@link String} value by key. + * + * @param key The key to look for. + * @return The first {@link String} associated with the given key or {@code null} if there + * is no such key or the value is of a different type. + * @hide + */ + @Nullable + public String getPropertyString(@NonNull String key) { + String[] propertyArray = getPropertyStringArray(key); + if (ArrayUtils.isEmpty(propertyArray)) { + return null; + } + warnIfSinglePropertyTooLong("String", key, propertyArray.length); + return propertyArray[0]; + } + + /** + * Retrieve a {@link Long} value by key. + * + * @param key The key to look for. + * @return The first {@link Long} associated with the given key or {@code null} if there + * is no such key or the value is of a different type. + * @hide + */ + @Nullable + public Long getPropertyLong(@NonNull String key) { + long[] propertyArray = getPropertyLongArray(key); + if (ArrayUtils.isEmpty(propertyArray)) { + return null; + } + warnIfSinglePropertyTooLong("Long", key, propertyArray.length); + return propertyArray[0]; + } + + /** + * Retrieve a {@link Double} value by key. + * + * @param key The key to look for. + * @return The first {@link Double} associated with the given key or {@code null} if there + * is no such key or the value is of a different type. + * @hide + */ + @Nullable + public Double getPropertyDouble(@NonNull String key) { + double[] propertyArray = getPropertyDoubleArray(key); + // TODO(tytytyww): Add support double array to ArraysUtils.isEmpty(). + if (propertyArray == null || propertyArray.length == 0) { + return null; + } + warnIfSinglePropertyTooLong("Double", key, propertyArray.length); + return propertyArray[0]; + } + + /** + * Retrieve a {@link Boolean} value by key. + * + * @param key The key to look for. + * @return The first {@link Boolean} associated with the given key or {@code null} if there + * is no such key or the value is of a different type. + * @hide + */ + @Nullable + public Boolean getPropertyBoolean(@NonNull String key) { + boolean[] propertyArray = getPropertyBooleanArray(key); + if (ArrayUtils.isEmpty(propertyArray)) { + return null; + } + warnIfSinglePropertyTooLong("Boolean", key, propertyArray.length); + return propertyArray[0]; + } + + /** Prints a warning to logcat if the given propertyLength is greater than 1. */ + private static void warnIfSinglePropertyTooLong( + @NonNull String propertyType, @NonNull String key, int propertyLength) { + if (propertyLength > 1) { + Log.w(TAG, "The value for \"" + key + "\" contains " + propertyLength + + " elements. Only the first one will be returned from " + + "getProperty" + propertyType + "(). Try getProperty" + propertyType + + "Array()."); + } + } + + /** + * Retrieve a repeated {@code String} property by key. + * + * @param key The key to look for. + * @return The {@code String[]} associated with the given key, or {@code null} if no value + * is set or the value is of a different type. + * @hide + */ + @Nullable + public String[] getPropertyStringArray(@NonNull String key) { + return getAndCastPropertyArray(key, String[].class); + } + + /** + * Retrieve a repeated {@code long} property by key. + * + * @param key The key to look for. + * @return The {@code long[]} associated with the given key, or {@code null} if no value is + * set or the value is of a different type. + * @hide + */ + @Nullable + public long[] getPropertyLongArray(@NonNull String key) { + return getAndCastPropertyArray(key, long[].class); + } + + /** + * Retrieve a repeated {@code double} property by key. + * + * @param key The key to look for. + * @return The {@code double[]} associated with the given key, or {@code null} if no value + * is set or the value is of a different type. + * @hide + */ + @Nullable + public double[] getPropertyDoubleArray(@NonNull String key) { + return getAndCastPropertyArray(key, double[].class); + } + + /** + * Retrieve a repeated {@code boolean} property by key. + * + * @param key The key to look for. + * @return The {@code boolean[]} associated with the given key, or {@code null} if no value + * is set or the value is of a different type. + * @hide + */ + @Nullable + public boolean[] getPropertyBooleanArray(@NonNull String key) { + return getAndCastPropertyArray(key, boolean[].class); + } + + /** + * Gets a repeated property of the given key, and casts it to the given class type, which + * must be an array class type. + */ + @Nullable + private <T> T getAndCastPropertyArray(@NonNull String key, @NonNull Class<T> tClass) { + Object value = mPropertyBundle.get(key); + if (value == null) { + return null; + } + try { + return tClass.cast(value); + } catch (ClassCastException e) { + Log.w(TAG, "Error casting to requested type for key \"" + key + "\"", e); + return null; + } + } + + @Override + public boolean equals(@Nullable Object other) { + // Check only proto's equality is sufficient here since all properties in + // mPropertyBundle are ordered by keys and stored in proto. + if (this == other) { + return true; + } + if (!(other instanceof Document)) { + return false; + } + Document otherDocument = (Document) other; + return this.mProto.equals(otherDocument.mProto); + } + + @Override + public int hashCode() { + // Hash only proto is sufficient here since all properties in mPropertyBundle are + // ordered by keys and stored in proto. + return mProto.hashCode(); + } + + @Override + public String toString() { + return mProto.toString(); + } + + /** + * The builder class for {@link Document}. + * + * @param <BuilderType> Type of subclass who extend this. + * @hide + */ + public static class Builder<BuilderType extends Builder> { + + private final Bundle mPropertyBundle = new Bundle(); + private final DocumentProto.Builder mProtoBuilder = DocumentProto.newBuilder(); + private final BuilderType mBuilderTypeInstance; + + /** + * Create a new {@link Document.Builder}. + * + * @param uri The uri of {@link Document}. + * @param schemaType The schema type of the {@link Document}. The passed-in + * {@code schemaType} must be defined using {@link AppSearchManager#setSchema} prior + * to inserting a document of this {@code schemaType} into the AppSearch index using + * {@link AppSearchManager#put}. Otherwise, the document will be rejected by + * {@link AppSearchManager#put}. + * @hide + */ + protected Builder(@NonNull String uri, @NonNull String schemaType) { + mBuilderTypeInstance = (BuilderType) this; + mProtoBuilder.setUri(uri).setSchema(schemaType); + // Set current timestamp for creation timestamp by default. + setCreationTimestampSecs( + TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())); + } + + /** + * Set the score of the {@link Document}. + * + * <p>The score is a query-independent measure of the document's quality, relative to + * other {@link Document}s of the same type. + * + * @throws IllegalArgumentException If the provided value is negative. + * @hide + */ + @NonNull + public BuilderType setScore(@IntRange(from = 0, to = Integer.MAX_VALUE) int score) { + if (score < 0) { + throw new IllegalArgumentException("Document score cannot be negative"); + } + mProtoBuilder.setScore(score); + return mBuilderTypeInstance; + } + + /** + * Set the creation timestamp in seconds of the {@link Document}. + * + * @hide + */ + @NonNull + public BuilderType setCreationTimestampSecs( + @CurrentTimeSecondsLong long creationTimestampSecs) { + mProtoBuilder.setCreationTimestampSecs(creationTimestampSecs); + return mBuilderTypeInstance; + } + + /** + * Sets one or multiple {@code String} values for a property, replacing its previous + * values. + * + * @param key The key associated with the {@code values}. + * @param values The {@code String} values of the property. + * @hide + */ + @NonNull + public BuilderType setProperty(@NonNull String key, @NonNull String... values) { + putInBundle(mPropertyBundle, key, values); + return mBuilderTypeInstance; + } + + /** + * Sets one or multiple {@code boolean} values for a property, replacing its previous + * values. + * + * @param key The key associated with the {@code values}. + * @param values The {@code boolean} values of the schema.org property. + * @hide + */ + @NonNull + public BuilderType setProperty(@NonNull String key, @NonNull boolean... values) { + putInBundle(mPropertyBundle, key, values); + return mBuilderTypeInstance; + } + + /** + * Sets one or multiple {@code long} values for a property, replacing its previous + * values. + * + * @param key The key associated with the {@code values}. + * @param values The {@code long} values of the schema.org property. + * @hide + */ + @NonNull + public BuilderType setProperty(@NonNull String key, @NonNull long... values) { + putInBundle(mPropertyBundle, key, values); + return mBuilderTypeInstance; + } + + /** + * Sets one or multiple {@code double} values for a property, replacing its previous + * values. + * + * @param key The key associated with the {@code values}. + * @param values The {@code double} values of the schema.org property. + * @hide + */ + @NonNull + public BuilderType setProperty(@NonNull String key, @NonNull double... values) { + putInBundle(mPropertyBundle, key, values); + return mBuilderTypeInstance; + } + + private static void putInBundle( + @NonNull Bundle bundle, @NonNull String key, @NonNull String... values) + throws IllegalArgumentException { + Objects.requireNonNull(key); + Objects.requireNonNull(values); + validateRepeatedPropertyLength(key, values.length); + for (int i = 0; i < values.length; i++) { + if (values[i] == null) { + throw new IllegalArgumentException("The String at " + i + " is null."); + } else if (values[i].length() > MAX_STRING_LENGTH) { + throw new IllegalArgumentException("The String at " + i + " length is: " + + values[i].length() + ", which exceeds length limit: " + + MAX_STRING_LENGTH + "."); + } + } + bundle.putStringArray(key, values); + } + + private static void putInBundle( + @NonNull Bundle bundle, @NonNull String key, @NonNull boolean... values) { + Objects.requireNonNull(key); + Objects.requireNonNull(values); + validateRepeatedPropertyLength(key, values.length); + bundle.putBooleanArray(key, values); + } + + private static void putInBundle( + @NonNull Bundle bundle, @NonNull String key, @NonNull double... values) { + Objects.requireNonNull(key); + Objects.requireNonNull(values); + validateRepeatedPropertyLength(key, values.length); + bundle.putDoubleArray(key, values); + } + + private static void putInBundle( + @NonNull Bundle bundle, @NonNull String key, @NonNull long... values) { + Objects.requireNonNull(key); + Objects.requireNonNull(values); + validateRepeatedPropertyLength(key, values.length); + bundle.putLongArray(key, values); + } + + 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) { + throw new IllegalArgumentException( + "Repeated property \"" + key + "\" has length " + length + + ", which exceeds the limit of " + + MAX_REPEATED_PROPERTY_LENGTH); + } + } + + /** + * Builds the {@link Document} object. + * @hide + */ + public Document build() { + // Build proto by sorting the keys in propertyBundle to exclude the influence of + // order. Therefore documents will generate same proto as long as the contents are + // same. Note that the order of repeated fields is still preserved. + ArrayList<String> keys = new ArrayList<>(mPropertyBundle.keySet()); + Collections.sort(keys); + for (String key : keys) { + Object values = mPropertyBundle.get(key); + PropertyProto.Builder propertyProto = PropertyProto.newBuilder().setName(key); + if (values instanceof boolean[]) { + for (boolean value : (boolean[]) values) { + propertyProto.addBooleanValues(value); + } + } else if (values instanceof long[]) { + for (long value : (long[]) values) { + propertyProto.addInt64Values(value); + } + } else if (values instanceof double[]) { + for (double value : (double[]) values) { + propertyProto.addDoubleValues(value); + } + } else if (values instanceof String[]) { + for (String value : (String[]) values) { + propertyProto.addStringValues(value); + } + } else { + throw new IllegalStateException( + "Property \"" + key + "\" has unsupported value type \"" + + values.getClass().getSimpleName() + "\""); + } + mProtoBuilder.addProperties(propertyProto); + } + return new Document(mProtoBuilder.build(), mPropertyBundle); + } + } + } + + /** + * Encapsulates a {@link Document} that represent an email. + * + * <p>This class is a higher level implement of {@link Document}. + * + * <p>This class will eventually migrate to Jetpack, where it will become public API. + * + * @hide + */ + public static class Email extends Document { + + /** The name of the schema type for {@link Email} documents.*/ + public static final String SCHEMA_TYPE = "builtin:Email"; + + private static final String KEY_FROM = "from"; + private static final String KEY_TO = "to"; + private static final String KEY_CC = "cc"; + private static final String KEY_BCC = "bcc"; + private static final String KEY_SUBJECT = "subject"; + private static final String KEY_BODY = "body"; + + /** + * Creates a new {@link Email} from the contents of an existing {@link Document}. + * + * @param document The {@link Document} containing the email content. + */ + public Email(@NonNull Document document) { + super(document); + } + + /** + * Creates a new {@link Email.Builder}. + * + * @param uri The uri of {@link Email}. + */ + public static Builder newBuilder(@NonNull String uri) { + return new Builder(uri); + } + + /** + * Get the from address of {@link Email}. + * + * @return Returns the subject of {@link Email} or {@code null} if it's not been set yet. + * @hide + */ + @Nullable + public String getFrom() { + return getPropertyString(KEY_FROM); + } + + /** + * Get the destination address of {@link Email}. + * + * @return Returns the destination address of {@link Email} or {@code null} if it's not been + * set yet. + * @hide + */ + @Nullable + public String[] getTo() { + return getPropertyStringArray(KEY_TO); + } + + /** + * Get the CC list of {@link Email}. + * + * @return Returns the CC list of {@link Email} or {@code null} if it's not been set yet. + * @hide + */ + @Nullable + public String[] getCc() { + return getPropertyStringArray(KEY_CC); + } + + /** + * Get the BCC list of {@link Email}. + * + * @return Returns the BCC list of {@link Email} or {@code null} if it's not been set yet. + * @hide + */ + @Nullable + public String[] getBcc() { + return getPropertyStringArray(KEY_BCC); + } + + /** + * Get the subject of {@link Email}. + * + * @return Returns the value subject of {@link Email} or {@code null} if it's not been set + * yet. + * @hide + */ + @Nullable + public String getSubject() { + return getPropertyString(KEY_SUBJECT); + } + + /** + * Get the body of {@link Email}. + * + * @return Returns the body of {@link Email} or {@code null} if it's not been set yet. + * @hide + */ + @Nullable + public String getBody() { + return getPropertyString(KEY_BODY); + } + + /** + * The builder class for {@link Email}. + * @hide + */ + public static class Builder extends Document.Builder<Email.Builder> { + + /** + * Create a new {@link Email.Builder} + * @param uri The Uri of the Email. + * @hide + */ + private Builder(@NonNull String uri) { + super(uri, SCHEMA_TYPE); + } + + /** + * Set the from address of {@link Email} + * @hide + */ + @NonNull + public Email.Builder setFrom(@NonNull String from) { + setProperty(KEY_FROM, from); + return this; + } + + /** + * Set the destination address of {@link Email} + * @hide + */ + @NonNull + public Email.Builder setTo(@NonNull String... to) { + setProperty(KEY_TO, to); + return this; + } + + /** + * Set the CC list of {@link Email} + * @hide + */ + @NonNull + public Email.Builder setCc(@NonNull String... cc) { + setProperty(KEY_CC, cc); + return this; + } + + /** + * Set the BCC list of {@link Email} + * @hide + */ + @NonNull + public Email.Builder setBcc(@NonNull String... bcc) { + setProperty(KEY_BCC, bcc); + return this; + } + + /** + * Set the subject of {@link Email} + * @hide + */ + @NonNull + public Email.Builder setSubject(@NonNull String subject) { + setProperty(KEY_SUBJECT, subject); + return this; + } + + /** + * Set the body of {@link Email} + * @hide + */ + @NonNull + public Email.Builder setBody(@NonNull String body) { + setProperty(KEY_BODY, body); + return this; + } + + /** + * Builds the {@link Email} object. + * + * @hide + */ + @NonNull + @Override + public Email build() { + return new Email(super.build()); + } + } + } +} diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java index a8ee35c129eb..83195dc73db6 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java @@ -15,21 +15,116 @@ */ package android.app.appsearch; +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; import android.annotation.SystemService; +import android.app.appsearch.AppSearch.Document; import android.content.Context; +import android.os.RemoteException; + +import com.android.internal.infra.AndroidFuture; + +import com.google.android.icing.proto.SchemaProto; + +import java.util.List; +import java.util.concurrent.Executor; +import java.util.function.Consumer; /** - * TODO(b/142567528): add comments when implement this class + * This class provides access to the centralized AppSearch index maintained by the system. + * + * <p>Apps can index structured text documents with AppSearch, which can then be retrieved through + * the query API. + * * @hide */ @SystemService(Context.APP_SEARCH_SERVICE) public class AppSearchManager { private final IAppSearchManager mService; + + /** @hide */ + public AppSearchManager(@NonNull IAppSearchManager service) { + mService = service; + } + /** - * TODO(b/142567528): add comments when implement this class + * Sets the schema being used by documents provided to the #put method. + * + * <p>This operation is performed asynchronously. On success, the provided callback will be + * called with {@code null}. On failure, the provided callback will be called with a + * {@link Throwable} describing the failure. + * + * <p>It is a no-op to set the same schema as has been previously set; this is handled + * efficiently. + * + * <p>AppSearch automatically handles the following types of schema changes: + * <ul> + * <li>Addition of new types (No changes to storage or index) + * <li>Removal of an existing type (All documents of the removed type are deleted) + * <li>Addition of new 'optional' property to a type (No changes to storage or index) + * <li>Removal of existing property of any cardinality (All documents reindexed) + * </ul> + * + * <p>This method will return an error when attempting to make the following types of changes: + * <ul> + * <li>Changing the type of an existing property + * <li>Adding a 'required' property + * </ul> + * + * @param schema The schema config for this app. + * @param executor Executor on which to invoke the callback. + * @param callback Callback to receive errors resulting from setting the schema. If the + * operation succeeds, the callback will be invoked with {@code null}. + * * @hide */ - public AppSearchManager(IAppSearchManager service) { - mService = service; + // TODO(b/143789408): linkify #put after that API is created + // TODO(b/145635424): add a 'force' param to setSchema after the corresponding API is finalized + // in Icing Library + // TODO(b/145635424): Update the documentation above once the Schema mutation APIs of Icing + // Library are finalized + public void setSchema( + @NonNull AppSearchSchema schema, + @NonNull @CallbackExecutor Executor executor, + @NonNull Consumer<? super Throwable> callback) { + SchemaProto schemaProto = schema.getProto(); + byte[] schemaBytes = schemaProto.toByteArray(); + AndroidFuture<Void> future = new AndroidFuture<>(); + try { + mService.setSchema(schemaBytes, future); + } catch (RemoteException e) { + future.completeExceptionally(e); + } + future.whenCompleteAsync((noop, err) -> callback.accept(err), executor); + } + + /** + * Index {@link Document} to AppSearch + * + * <p>You should not call this method directly; instead, use the {@code AppSearch#put()} API + * provided by JetPack. + * + * <p>The schema should be set via {@link #setSchema} method. + * + * @param documents {@link Document Documents} that need to be indexed. + * @param executor Executor on which to invoke the callback. + * @param callback Callback to receive errors resulting from setting the schema. If the + * operation succeeds, the callback will be invoked with {@code null}. + */ + public void put(@NonNull List<Document> documents, + @NonNull @CallbackExecutor Executor executor, + @NonNull Consumer<? super Throwable> callback) { + AndroidFuture<Void> future = new AndroidFuture<>(); + for (Document document : documents) { + // TODO(b/146386470) batching Document protos + try { + mService.put(document.getProto().toByteArray(), future); + } catch (RemoteException e) { + future.completeExceptionally(e); + break; + } + } + // TODO(b/147614371) Fix error report for multiple documents. + future.whenCompleteAsync((noop, err) -> callback.accept(err), executor); } } diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java new file mode 100644 index 000000000000..7e5f187b88c9 --- /dev/null +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java @@ -0,0 +1,426 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.appsearch; + +import android.annotation.IntDef; +import android.annotation.NonNull; + +import com.android.internal.annotations.VisibleForTesting; + +import com.google.android.icing.proto.PropertyConfigProto; +import com.google.android.icing.proto.SchemaProto; +import com.google.android.icing.proto.SchemaTypeConfigProto; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Representation of the AppSearch Schema. + * + * <p>The schema is the set of document types, properties, and config (like tokenization type) + * understood by AppSearch for this app. + * + * @hide + */ +public final class AppSearchSchema { + private final SchemaProto mProto; + + private AppSearchSchema(SchemaProto proto) { + mProto = proto; + } + + /** Creates a new {@link AppSearchSchema.Builder}. */ + @NonNull + public static AppSearchSchema.Builder newBuilder() { + return new AppSearchSchema.Builder(); + } + + /** Creates a new {@link SchemaType.Builder}. */ + @NonNull + public static SchemaType.Builder newSchemaTypeBuilder(@NonNull String typeName) { + return new SchemaType.Builder(typeName); + } + + /** Creates a new {@link PropertyConfig.Builder}. */ + @NonNull + public static PropertyConfig.Builder newPropertyBuilder(@NonNull String propertyName) { + return new PropertyConfig.Builder(propertyName); + } + + /** Creates a new {@link IndexingConfig.Builder}. */ + @NonNull + public static IndexingConfig.Builder newIndexingConfigBuilder() { + return new IndexingConfig.Builder(); + } + + /** + * Returns the schema proto populated by the {@link AppSearchSchema} builders. + * @hide + */ + @NonNull + @VisibleForTesting + public SchemaProto getProto() { + return mProto; + } + + /** Builder for {@link AppSearchSchema objects}. */ + public static final class Builder { + private final SchemaProto.Builder mProtoBuilder = SchemaProto.newBuilder(); + + private Builder() {} + + /** Adds a supported type to this app's AppSearch schema. */ + @NonNull + public AppSearchSchema.Builder addType(@NonNull SchemaType schemaType) { + mProtoBuilder.addTypes(schemaType.mProto); + return this; + } + + /** + * Constructs a new {@link AppSearchSchema} from the contents of this builder. + * + * <p>After calling this method, the builder must no longer be used. + */ + @NonNull + public AppSearchSchema build() { + return new AppSearchSchema(mProtoBuilder.build()); + } + } + + /** + * Represents a type of a document. + * + * <p>For example, an e-mail message or a music recording could be a schema type. + */ + public static final class SchemaType { + private final SchemaTypeConfigProto mProto; + + private SchemaType(SchemaTypeConfigProto proto) { + mProto = proto; + } + + /** Builder for {@link SchemaType} objects. */ + public static final class Builder { + private final SchemaTypeConfigProto.Builder mProtoBuilder = + SchemaTypeConfigProto.newBuilder(); + + private Builder(@NonNull String typeName) { + mProtoBuilder.setSchemaType(typeName); + } + + /** Adds a property to the given type. */ + @NonNull + public SchemaType.Builder addProperty(@NonNull PropertyConfig propertyConfig) { + mProtoBuilder.addProperties(propertyConfig.mProto); + return this; + } + + /** + * Constructs a new {@link SchemaType} from the contents of this builder. + * + * <p>After calling this method, the builder must no longer be used. + */ + @NonNull + public SchemaType build() { + return new SchemaType(mProtoBuilder.build()); + } + } + } + + /** + * Configuration for a single property (field) of a document type. + * + * <p>For example, an {@code EmailMessage} would be a type and the {@code subject} would be + * a property. + */ + public static final class PropertyConfig { + /** Physical data-types of the contents of the property. */ + // NOTE: The integer values of these constants must match the proto enum constants in + // com.google.android.icing.proto.PropertyConfigProto.DataType.Code. + @IntDef(prefix = {"DATA_TYPE_"}, value = { + DATA_TYPE_STRING, + DATA_TYPE_INT64, + DATA_TYPE_DOUBLE, + DATA_TYPE_BOOLEAN, + DATA_TYPE_BYTES, + DATA_TYPE_DOCUMENT, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DataType {} + + public static final int DATA_TYPE_STRING = 1; + public static final int DATA_TYPE_INT64 = 2; + public static final int DATA_TYPE_DOUBLE = 3; + public static final int DATA_TYPE_BOOLEAN = 4; + + /** Unstructured BLOB. */ + public static final int DATA_TYPE_BYTES = 5; + + /** + * Indicates that the property itself is an Document, making it part a hierarchical + * Document schema. Any property using this DataType MUST have a valid + * {@code schemaType}. + */ + public static final int DATA_TYPE_DOCUMENT = 6; + + /** The cardinality of the property (whether it is required, optional or repeated). */ + // NOTE: The integer values of these constants must match the proto enum constants in + // com.google.android.icing.proto.PropertyConfigProto.Cardinality.Code. + @IntDef(prefix = {"CARDINALITY_"}, value = { + CARDINALITY_REPEATED, + CARDINALITY_OPTIONAL, + CARDINALITY_REQUIRED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Cardinality {} + + /** Any number of items (including zero) [0...*]. */ + public static final int CARDINALITY_REPEATED = 1; + + /** Zero or one value [0,1]. */ + public static final int CARDINALITY_OPTIONAL = 2; + + /** Exactly one value [1]. */ + public static final int CARDINALITY_REQUIRED = 3; + + private final PropertyConfigProto mProto; + + private PropertyConfig(PropertyConfigProto proto) { + mProto = proto; + } + + /** + * Builder for {@link PropertyConfig}. + * + * <p>The following properties must be set, or {@link PropertyConfig} construction will + * fail: + * <ul> + * <li>dataType + * <li>cardinality + * </ul> + * + * <p>In addition, if {@code schemaType} is {@link #DATA_TYPE_DOCUMENT}, {@code schemaType} + * is also required. + */ + public static final class Builder { + private final PropertyConfigProto.Builder mProtoBuilder = + PropertyConfigProto.newBuilder(); + + private Builder(String propertyName) { + mProtoBuilder.setPropertyName(propertyName); + } + + /** + * Type of data the property contains (e.g. string, int, bytes, etc). + * + * <p>This property must be set. + */ + @NonNull + public PropertyConfig.Builder setDataType(@DataType int dataType) { + PropertyConfigProto.DataType.Code dataTypeProto = + PropertyConfigProto.DataType.Code.forNumber(dataType); + if (dataTypeProto == null) { + throw new IllegalArgumentException("Invalid dataType: " + dataType); + } + mProtoBuilder.setDataType(dataTypeProto); + return this; + } + + /** + * The logical schema-type of the contents of this property. + * + * <p>Only required when {@link #setDataType(int)} is set to + * {@link #DATA_TYPE_DOCUMENT}. Otherwise, it is ignored. + */ + @NonNull + public PropertyConfig.Builder setSchemaType(@NonNull String schemaType) { + mProtoBuilder.setSchemaType(schemaType); + return this; + } + + /** + * The cardinality of the property (whether it is optional, required or repeated). + * + * <p>This property must be set. + */ + @NonNull + public PropertyConfig.Builder setCardinality(@Cardinality int cardinality) { + PropertyConfigProto.Cardinality.Code cardinalityProto = + PropertyConfigProto.Cardinality.Code.forNumber(cardinality); + if (cardinalityProto == null) { + throw new IllegalArgumentException("Invalid cardinality: " + cardinality); + } + mProtoBuilder.setCardinality(cardinalityProto); + return this; + } + + /** + * Configures how this property should be indexed. + * + * <p>If this is not supplied, the property will not be indexed at all. + */ + @NonNull + public PropertyConfig.Builder setIndexingConfig( + @NonNull IndexingConfig indexingConfig) { + mProtoBuilder.setIndexingConfig(indexingConfig.mProto); + return this; + } + + /** + * Constructs a new {@link PropertyConfig} from the contents of this builder. + * + * <p>After calling this method, the builder must no longer be used. + * + * @throws IllegalSchemaException If the property is not correctly populated (e.g. + * missing {@code dataType}). + */ + @NonNull + public PropertyConfig build() { + if (mProtoBuilder.getDataType() == PropertyConfigProto.DataType.Code.UNKNOWN) { + throw new IllegalSchemaException("Missing field: dataType"); + } + if (mProtoBuilder.getSchemaType().isEmpty() + && mProtoBuilder.getDataType() + == PropertyConfigProto.DataType.Code.DOCUMENT) { + throw new IllegalSchemaException( + "Missing field: schemaType (required for configs with " + + "dataType = DOCUMENT)"); + } + if (mProtoBuilder.getCardinality() + == PropertyConfigProto.Cardinality.Code.UNKNOWN) { + throw new IllegalSchemaException("Missing field: cardinality"); + } + return new PropertyConfig(mProtoBuilder.build()); + } + } + } + + /** Configures how a property should be indexed so that it can be retrieved by queries. */ + public static final class IndexingConfig { + /** Encapsulates the configurations on how AppSearch should query/index these terms. */ + // NOTE: The integer values of these constants must match the proto enum constants in + // com.google.android.icing.proto.TermMatchType.Code. + @IntDef(prefix = {"TERM_MATCH_TYPE_"}, value = { + TERM_MATCH_TYPE_UNKNOWN, + TERM_MATCH_TYPE_EXACT_ONLY, + TERM_MATCH_TYPE_PREFIX, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface TermMatchType {} + + /** + * Content in this property will not be tokenized or indexed. + * + * <p>Useful if the data type is not made up of terms (e.g. + * {@link PropertyConfig#DATA_TYPE_DOCUMENT} or {@link PropertyConfig#DATA_TYPE_BYTES} + * type). All the properties inside the nested property won't be indexed regardless of the + * value of {@code termMatchType} for the nested properties. + */ + public static final int TERM_MATCH_TYPE_UNKNOWN = 0; + + /** + * Content in this property should only be returned for queries matching the exact tokens + * appearing in this property. + * + * <p>Ex. A property with "fool" should NOT match a query for "foo". + */ + public static final int TERM_MATCH_TYPE_EXACT_ONLY = 1; + + /** + * Content in this property should be returned for queries that are either exact matches or + * query matches of the tokens appearing in this property. + * + * <p>Ex. A property with "fool" <b>should</b> match a query for "foo". + */ + public static final int TERM_MATCH_TYPE_PREFIX = 2; + + /** Configures how tokens should be extracted from this property. */ + // NOTE: The integer values of these constants must match the proto enum constants in + // com.google.android.icing.proto.IndexingConfig.TokenizerType.Code. + @IntDef(prefix = {"TOKENIZER_TYPE_"}, value = { + TOKENIZER_TYPE_NONE, + TOKENIZER_TYPE_PLAIN, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface TokenizerType {} + + /** + * It is only valid for tokenizer_type to be 'NONE' if the data type is + * {@link PropertyConfig#DATA_TYPE_DOCUMENT}. + */ + public static final int TOKENIZER_TYPE_NONE = 0; + + /** Tokenization for plain text. */ + public static final int TOKENIZER_TYPE_PLAIN = 1; + + private final com.google.android.icing.proto.IndexingConfig mProto; + + private IndexingConfig(com.google.android.icing.proto.IndexingConfig proto) { + mProto = proto; + } + + /** + * Builder for {@link IndexingConfig} objects. + * + * <p>You may skip adding an {@link IndexingConfig} for a property, which is equivalent to + * an {@link IndexingConfig} having {@code termMatchType} equal to + * {@link #TERM_MATCH_TYPE_UNKNOWN}. In this case the property will not be indexed. + */ + public static final class Builder { + private final com.google.android.icing.proto.IndexingConfig.Builder mProtoBuilder = + com.google.android.icing.proto.IndexingConfig.newBuilder(); + + private Builder() {} + + /** Configures how the content of this property should be matched in the index. */ + @NonNull + public IndexingConfig.Builder setTermMatchType(@TermMatchType int termMatchType) { + com.google.android.icing.proto.TermMatchType.Code termMatchTypeProto = + com.google.android.icing.proto.TermMatchType.Code.forNumber(termMatchType); + if (termMatchTypeProto == null) { + throw new IllegalArgumentException("Invalid termMatchType: " + termMatchType); + } + mProtoBuilder.setTermMatchType(termMatchTypeProto); + return this; + } + + /** Configures how this property should be tokenized (split into words). */ + @NonNull + public IndexingConfig.Builder setTokenizerType(@TokenizerType int tokenizerType) { + com.google.android.icing.proto.IndexingConfig.TokenizerType.Code + tokenizerTypeProto = + com.google.android.icing.proto.IndexingConfig + .TokenizerType.Code.forNumber(tokenizerType); + if (tokenizerTypeProto == null) { + throw new IllegalArgumentException("Invalid tokenizerType: " + tokenizerType); + } + mProtoBuilder.setTokenizerType(tokenizerTypeProto); + return this; + } + + /** + * Constructs a new {@link IndexingConfig} from the contents of this builder. + * + * <p>After calling this method, the builder must no longer be used. + */ + @NonNull + public IndexingConfig build() { + return new IndexingConfig(mProtoBuilder.build()); + } + } + } +} diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl index f0f4f512d769..fc83d8ccbd4a 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl +++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl @@ -14,6 +14,19 @@ * limitations under the License. */ package android.app.appsearch; + +import com.android.internal.infra.AndroidFuture; + /** {@hide} */ interface IAppSearchManager { + /** + * Sets the schema. + * + * @param schemaProto serialized SchemaProto + * @param callback {@link AndroidFuture}<{@link Void}>. Will be completed with + * {@code null} upon successful completion of the setSchema call, or completed exceptionally + * if setSchema fails. + */ + void setSchema(in byte[] schemaProto, in AndroidFuture callback); + void put(in byte[] documentBytes, in AndroidFuture callback); } diff --git a/apex/appsearch/framework/java/android/app/appsearch/IllegalSchemaException.java b/apex/appsearch/framework/java/android/app/appsearch/IllegalSchemaException.java new file mode 100644 index 000000000000..f9e528cd2951 --- /dev/null +++ b/apex/appsearch/framework/java/android/app/appsearch/IllegalSchemaException.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.appsearch; + +import android.annotation.NonNull; + +/** + * Indicates that a {@link android.app.appsearch.AppSearchSchema} has logical inconsistencies such + * as unpopulated mandatory fields or illegal combinations of parameters. + * + * @hide + */ +public class IllegalSchemaException extends IllegalArgumentException { + /** + * Constructs a new {@link IllegalSchemaException}. + * + * @param message A developer-readable description of the issue with the bundle. + */ + public IllegalSchemaException(@NonNull String message) { + super(message); + } +} diff --git a/apex/appsearch/service/Android.bp b/apex/appsearch/service/Android.bp index e7abcd9a645a..04f385e8c6f6 100644 --- a/apex/appsearch/service/Android.bp +++ b/apex/appsearch/service/Android.bp @@ -12,18 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. java_library { - name: "service-appsearch", - installable: true, - srcs: [ - "java/**/*.java", - ], - libs: [ - "framework", - "services.core", - "framework-appsearch", - ], - static_libs: [ - "icing-java-proto-lite", - ], - apex_available: [ "com.android.appsearch" ], + name: "service-appsearch", + installable: true, + srcs: ["java/**/*.java"], + libs: [ + "framework", + "framework-appsearch", + "services.core", + ], + apex_available: ["com.android.appsearch"], } 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 4d44d9d04806..ce7e04c8ce04 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java @@ -17,8 +17,16 @@ package com.android.server.appsearch; import android.app.appsearch.IAppSearchManager; import android.content.Context; +import android.os.Binder; +import android.os.UserHandle; +import com.android.internal.infra.AndroidFuture; +import com.android.internal.util.Preconditions; import com.android.server.SystemService; +import com.android.server.appsearch.impl.AppSearchImpl; +import com.android.server.appsearch.impl.ImplInstanceManager; + +import com.google.android.icing.proto.SchemaProto; /** * TODO(b/142567528): add comments when implement this class @@ -35,5 +43,32 @@ public class AppSearchManagerService extends SystemService { } private class Stub extends IAppSearchManager.Stub { + @Override + public void setSchema(byte[] schemaBytes, AndroidFuture callback) { + Preconditions.checkNotNull(schemaBytes); + Preconditions.checkNotNull(callback); + int callingUid = Binder.getCallingUidOrThrow(); + int callingUserId = UserHandle.getUserId(callingUid); + long callingIdentity = Binder.clearCallingIdentity(); + try { + SchemaProto schema = SchemaProto.parseFrom(schemaBytes); + AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId); + impl.setSchema(callingUid, schema); + callback.complete(null); + } catch (Throwable t) { + callback.completeExceptionally(t); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + } + + @Override + public void put(byte[] documentBytes, AndroidFuture callback) { + try { + throw new UnsupportedOperationException("Put document not yet implemented"); + } catch (Throwable t) { + callback.completeExceptionally(t); + } + } } } diff --git a/apex/appsearch/service/java/com/android/server/appsearch/TEST_MAPPING b/apex/appsearch/service/java/com/android/server/appsearch/TEST_MAPPING index 08811f804fd1..ca5b8841ea49 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/TEST_MAPPING +++ b/apex/appsearch/service/java/com/android/server/appsearch/TEST_MAPPING @@ -10,6 +10,14 @@ "include-filter": "com.android.server.appsearch" } ] + }, + { + "name": "FrameworksCoreTests", + "options": [ + { + "include-filter": "android.app.appsearch" + } + ] } ] } diff --git a/apex/appsearch/service/java/com/android/server/appsearch/impl/AppSearchImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/impl/AppSearchImpl.java new file mode 100644 index 000000000000..7c97b0b8cf30 --- /dev/null +++ b/apex/appsearch/service/java/com/android/server/appsearch/impl/AppSearchImpl.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.appsearch.impl; + +import android.annotation.NonNull; +import android.annotation.UserIdInt; +import android.content.Context; + +import com.android.internal.annotations.VisibleForTesting; + +import com.google.android.icing.proto.PropertyConfigProto; +import com.google.android.icing.proto.SchemaProto; +import com.google.android.icing.proto.SchemaTypeConfigProto; + +/** + * Manages interaction with {@link FakeIcing} and other components to implement AppSearch + * functionality. + */ +public final class AppSearchImpl { + private final Context mContext; + private final @UserIdInt int mUserId; + private final FakeIcing mFakeIcing = new FakeIcing(); + + AppSearchImpl(@NonNull Context context, @UserIdInt int userId) { + mContext = context; + mUserId = userId; + } + + /** + * Updates the AppSearch schema for this app. + * + * @param callingUid The uid of the app calling AppSearch. + * @param origSchema The schema to set for this app. + */ + public void setSchema(int callingUid, @NonNull SchemaProto origSchema) { + // Rewrite schema type names to include the calling app's package and uid. + String typePrefix = getTypePrefix(callingUid); + SchemaProto.Builder schemaBuilder = origSchema.toBuilder(); + rewriteSchemaTypes(typePrefix, schemaBuilder); + + // TODO(b/145635424): Save in schema type map + // TODO(b/145635424): Apply the schema to Icing and report results + } + + /** + * Rewrites all types mentioned in the given {@code schemaBuilder} to prepend + * {@code typePrefix}. + * + * @param typePrefix The prefix to add + * @param schemaBuilder The schema to mutate + */ + @VisibleForTesting + void rewriteSchemaTypes( + @NonNull String typePrefix, @NonNull SchemaProto.Builder schemaBuilder) { + for (int typeIdx = 0; typeIdx < schemaBuilder.getTypesCount(); typeIdx++) { + SchemaTypeConfigProto.Builder typeConfigBuilder = + schemaBuilder.getTypes(typeIdx).toBuilder(); + + // Rewrite SchemaProto.types.schema_type + String newSchemaType = typePrefix + typeConfigBuilder.getSchemaType(); + typeConfigBuilder.setSchemaType(newSchemaType); + + // Rewrite SchemaProto.types.properties.schema_type + for (int propertyIdx = 0; + propertyIdx < typeConfigBuilder.getPropertiesCount(); + propertyIdx++) { + PropertyConfigProto.Builder propertyConfigBuilder = + typeConfigBuilder.getProperties(propertyIdx).toBuilder(); + if (!propertyConfigBuilder.getSchemaType().isEmpty()) { + String newPropertySchemaType = + typePrefix + propertyConfigBuilder.getSchemaType(); + propertyConfigBuilder.setSchemaType(newPropertySchemaType); + typeConfigBuilder.setProperties(propertyIdx, propertyConfigBuilder); + } + } + + schemaBuilder.setTypes(typeIdx, typeConfigBuilder); + } + } + + /** + * Returns a type prefix in a format like {@code com.example.package@1000/} or + * {@code com.example.sharedname:5678@1000/}. + */ + @NonNull + private String getTypePrefix(int callingUid) { + // For regular apps, this call will return the package name. If callingUid is an + // android:sharedUserId, this value may be another type of name and have a :uid suffix. + String callingUidName = mContext.getPackageManager().getNameForUid(callingUid); + if (callingUidName == null) { + // Not sure how this is possible --- maybe app was uninstalled? + throw new IllegalStateException("Failed to look up package name for uid " + callingUid); + } + return callingUidName + "@" + mUserId + "/"; + } +} diff --git a/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java b/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java index 3dbb5cffe908..02a79a11032f 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java @@ -36,8 +36,6 @@ import java.util.concurrent.atomic.AtomicInteger; * <p> * Currently, only queries by single exact term are supported. There is no support for persistence, * namespaces, i18n tokenization, or schema. - * - * @hide */ public class FakeIcing { private final AtomicInteger mNextDocId = new AtomicInteger(); diff --git a/apex/appsearch/service/java/com/android/server/appsearch/impl/ImplInstanceManager.java b/apex/appsearch/service/java/com/android/server/appsearch/impl/ImplInstanceManager.java new file mode 100644 index 000000000000..395e30e89dc0 --- /dev/null +++ b/apex/appsearch/service/java/com/android/server/appsearch/impl/ImplInstanceManager.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.appsearch.impl; + +import android.annotation.NonNull; +import android.annotation.UserIdInt; +import android.content.Context; +import android.util.SparseArray; + +/** + * Manages the lifecycle of instances of {@link AppSearchImpl}. + * + * <p>These instances are managed per unique device-user. + */ +public final class ImplInstanceManager { + private static final SparseArray<AppSearchImpl> sInstances = new SparseArray<>(); + + /** + * Gets an instance of AppSearchImpl for the given user. + * + * <p>If no AppSearchImpl instance exists for this user, Icing will be initialized and one will + * be created. + * + * @param context The Android context + * @param userId The multi-user userId of the device user calling AppSearch + * @return An initialized {@link AppSearchImpl} for this user + */ + @NonNull + public static AppSearchImpl getInstance(@NonNull Context context, @UserIdInt int userId) { + AppSearchImpl instance = sInstances.get(userId); + if (instance == null) { + synchronized (ImplInstanceManager.class) { + instance = sInstances.get(userId); + if (instance == null) { + instance = new AppSearchImpl(context, userId); + sInstances.put(userId, instance); + } + } + } + return instance; + } +} diff --git a/apex/jobscheduler/service/Android.bp b/apex/jobscheduler/service/Android.bp index c9d9d6c7d87a..69a9fd844729 100644 --- a/apex/jobscheduler/service/Android.bp +++ b/apex/jobscheduler/service/Android.bp @@ -9,6 +9,7 @@ java_library { ], libs: [ + "app-compat-annotations", "framework", "services.core", ], diff --git a/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING new file mode 100644 index 000000000000..8fbfb1daaf6f --- /dev/null +++ b/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING @@ -0,0 +1,22 @@ +{ + "presubmit": [ + { + "name": "FrameworksMockingServicesTests", + "file_patterns": [ + "DeviceIdleController\\.java" + ], + "options": [ + {"include-filter": "com.android.server.DeviceIdleControllerTest"}, + {"exclude-annotation": "androidx.test.filters.FlakyTest"} + ] + } + ], + "postsubmit": [ + { + "name": "FrameworksMockingServicesTests", + "options": [ + {"include-filter": "com.android.server"} + ] + } + ] +}
\ No newline at end of file diff --git a/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING new file mode 100644 index 000000000000..bc7a7d3bef7d --- /dev/null +++ b/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING @@ -0,0 +1,19 @@ +{ + "presubmit": [ + { + "name": "FrameworksMockingServicesTests", + "options": [ + {"include-filter": "com.android.server.DeviceIdleControllerTest"}, + {"exclude-annotation": "androidx.test.filters.FlakyTest"} + ] + } + ], + "postsubmit": [ + { + "name": "FrameworksMockingServicesTests", + "options": [ + {"include-filter": "com.android.server"} + ] + } + ] +}
\ No newline at end of file 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 9310762665db..102e8485aac5 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -37,12 +37,15 @@ import android.app.job.JobSnapshot; import android.app.job.JobWorkItem; import android.app.usage.UsageStatsManager; import android.app.usage.UsageStatsManagerInternal; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; @@ -54,6 +57,7 @@ import android.net.Uri; import android.os.BatteryStats; import android.os.BatteryStatsInternal; import android.os.Binder; +import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -67,6 +71,7 @@ import android.os.UserManagerInternal; import android.os.WorkSource; import android.provider.Settings; import android.text.format.DateUtils; +import android.util.ArrayMap; import android.util.KeyValueListParser; import android.util.Log; import android.util.Slog; @@ -85,6 +90,7 @@ import com.android.server.AppStateTracker; import com.android.server.DeviceIdleInternal; import com.android.server.FgThread; import com.android.server.LocalServices; +import com.android.server.compat.PlatformCompat; import com.android.server.job.JobSchedulerServiceDumpProto.ActiveJob; import com.android.server.job.JobSchedulerServiceDumpProto.PendingJob; import com.android.server.job.controllers.BackgroundJobsController; @@ -102,6 +108,9 @@ import com.android.server.job.restrictions.JobRestriction; import com.android.server.job.restrictions.ThermalStatusRestriction; import com.android.server.usage.AppStandbyInternal; import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener; +import com.android.server.utils.quota.Categorizer; +import com.android.server.utils.quota.Category; +import com.android.server.utils.quota.CountQuotaTracker; import libcore.util.EmptyArray; @@ -145,6 +154,16 @@ public class JobSchedulerService extends com.android.server.SystemService /** The maximum number of jobs that we allow an unprivileged app to schedule */ private static final int MAX_JOBS_PER_APP = 100; + /** + * {@link #schedule(JobInfo)}, {@link #scheduleAsPackage(JobInfo, String, int, String)}, and + * {@link #enqueue(JobInfo, JobWorkItem)} will throw a {@link IllegalStateException} if the app + * calls the APIs too frequently. + */ + @ChangeId + // This means the change will be enabled for target SDK larger than 29 (Q), meaning R and up. + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) + protected static final long CRASH_ON_EXCEEDED_LIMIT = 144363383L; + @VisibleForTesting public static Clock sSystemClock = Clock.systemUTC(); @@ -237,6 +256,10 @@ public class JobSchedulerService extends com.android.server.SystemService */ private final List<JobRestriction> mJobRestrictions; + private final CountQuotaTracker mQuotaTracker; + private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()"; + private final PlatformCompat mPlatformCompat; + /** * Queue of pending jobs. The JobServiceContext class will receive jobs from this list * when ready to execute them. @@ -276,6 +299,11 @@ public class JobSchedulerService extends com.android.server.SystemService final SparseIntArray mBackingUpUids = new SparseIntArray(); /** + * Cache of debuggable app status. + */ + final ArrayMap<String, Boolean> mDebuggableApps = new ArrayMap<>(); + + /** * Named indices into standby bucket arrays, for clarity in referring to * specific buckets' bookkeeping. */ @@ -315,6 +343,10 @@ public class JobSchedulerService extends com.android.server.SystemService final StateController sc = mControllers.get(controller); sc.onConstantsUpdatedLocked(); } + mQuotaTracker.setEnabled(mConstants.ENABLE_API_QUOTAS); + mQuotaTracker.setCountLimit(Category.SINGLE_CATEGORY, + mConstants.API_QUOTA_SCHEDULE_COUNT, + mConstants.API_QUOTA_SCHEDULE_WINDOW_MS); } catch (IllegalArgumentException e) { // Failed to parse the settings string, log this and move on // with defaults. @@ -466,6 +498,11 @@ public class JobSchedulerService extends com.android.server.SystemService private static final String KEY_CONN_CONGESTION_DELAY_FRAC = "conn_congestion_delay_frac"; private static final String KEY_CONN_PREFETCH_RELAX_FRAC = "conn_prefetch_relax_frac"; private static final String DEPRECATED_KEY_USE_HEARTBEATS = "use_heartbeats"; + private static final String KEY_ENABLE_API_QUOTAS = "enable_api_quotas"; + private static final String KEY_API_QUOTA_SCHEDULE_COUNT = "aq_schedule_count"; + private static final String KEY_API_QUOTA_SCHEDULE_WINDOW_MS = "aq_schedule_window_ms"; + private static final String KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION = + "aq_schedule_throw_exception"; private static final int DEFAULT_MIN_IDLE_COUNT = 1; private static final int DEFAULT_MIN_CHARGING_COUNT = 1; @@ -484,6 +521,10 @@ public class JobSchedulerService extends com.android.server.SystemService private static final long DEFAULT_MIN_EXP_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS; private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f; private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f; + private static final boolean DEFAULT_ENABLE_API_QUOTAS = true; + private static final int DEFAULT_API_QUOTA_SCHEDULE_COUNT = 250; + private static final long DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS = MINUTE_IN_MILLIS; + private static final boolean DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION = true; /** * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things @@ -618,6 +659,24 @@ public class JobSchedulerService extends com.android.server.SystemService */ public float CONN_PREFETCH_RELAX_FRAC = DEFAULT_CONN_PREFETCH_RELAX_FRAC; + /** + * Whether to enable quota limits on APIs. + */ + public boolean ENABLE_API_QUOTAS = DEFAULT_ENABLE_API_QUOTAS; + /** + * The maximum number of schedule() calls an app can make in a set amount of time. + */ + public int API_QUOTA_SCHEDULE_COUNT = DEFAULT_API_QUOTA_SCHEDULE_COUNT; + /** + * The time window that {@link #API_QUOTA_SCHEDULE_COUNT} should be evaluated over. + */ + public long API_QUOTA_SCHEDULE_WINDOW_MS = DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS; + /** + * Whether to throw an exception when an app hits its schedule quota limit. + */ + public boolean API_QUOTA_SCHEDULE_THROW_EXCEPTION = + DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION; + private final KeyValueListParser mParser = new KeyValueListParser(','); void updateConstantsLocked(String value) { @@ -678,6 +737,18 @@ public class JobSchedulerService extends com.android.server.SystemService DEFAULT_CONN_CONGESTION_DELAY_FRAC); CONN_PREFETCH_RELAX_FRAC = mParser.getFloat(KEY_CONN_PREFETCH_RELAX_FRAC, DEFAULT_CONN_PREFETCH_RELAX_FRAC); + + ENABLE_API_QUOTAS = mParser.getBoolean(KEY_ENABLE_API_QUOTAS, + DEFAULT_ENABLE_API_QUOTAS); + // Set a minimum value on the quota limit so it's not so low that it interferes with + // legitimate use cases. + API_QUOTA_SCHEDULE_COUNT = Math.max(250, + mParser.getInt(KEY_API_QUOTA_SCHEDULE_COUNT, DEFAULT_API_QUOTA_SCHEDULE_COUNT)); + API_QUOTA_SCHEDULE_WINDOW_MS = mParser.getDurationMillis( + KEY_API_QUOTA_SCHEDULE_WINDOW_MS, DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS); + API_QUOTA_SCHEDULE_THROW_EXCEPTION = mParser.getBoolean( + KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION, + DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION); } void dump(IndentingPrintWriter pw) { @@ -716,6 +787,12 @@ public class JobSchedulerService extends com.android.server.SystemService pw.printPair(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println(); pw.printPair(KEY_CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC).println(); + pw.printPair(KEY_ENABLE_API_QUOTAS, ENABLE_API_QUOTAS).println(); + pw.printPair(KEY_API_QUOTA_SCHEDULE_COUNT, API_QUOTA_SCHEDULE_COUNT).println(); + pw.printPair(KEY_API_QUOTA_SCHEDULE_WINDOW_MS, API_QUOTA_SCHEDULE_WINDOW_MS).println(); + pw.printPair(KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION, + API_QUOTA_SCHEDULE_THROW_EXCEPTION).println(); + pw.decreaseIndent(); } @@ -746,6 +823,12 @@ public class JobSchedulerService extends com.android.server.SystemService proto.write(ConstantsProto.MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME); proto.write(ConstantsProto.CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC); proto.write(ConstantsProto.CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC); + + proto.write(ConstantsProto.ENABLE_API_QUOTAS, ENABLE_API_QUOTAS); + proto.write(ConstantsProto.API_QUOTA_SCHEDULE_COUNT, API_QUOTA_SCHEDULE_COUNT); + proto.write(ConstantsProto.API_QUOTA_SCHEDULE_WINDOW_MS, API_QUOTA_SCHEDULE_WINDOW_MS); + proto.write(ConstantsProto.API_QUOTA_SCHEDULE_THROW_EXCEPTION, + API_QUOTA_SCHEDULE_THROW_EXCEPTION); } } @@ -847,6 +930,7 @@ public class JobSchedulerService extends com.android.server.SystemService for (int c = 0; c < mControllers.size(); ++c) { mControllers.get(c).onAppRemovedLocked(pkgName, pkgUid); } + mDebuggableApps.remove(pkgName); } } } else if (Intent.ACTION_USER_REMOVED.equals(action)) { @@ -972,6 +1056,49 @@ public class JobSchedulerService extends com.android.server.SystemService public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName, int userId, String tag) { + if (job.isPersisted()) { + // Only limit schedule calls for persisted jobs. + final String pkg = + packageName == null ? job.getService().getPackageName() : packageName; + if (!mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG)) { + Slog.e(TAG, userId + "-" + pkg + " has called schedule() too many times"); + // TODO(b/145551233): attempt to restrict app + if (mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION + && mPlatformCompat.isChangeEnabledByPackageName( + CRASH_ON_EXCEEDED_LIMIT, pkg, userId)) { + final boolean isDebuggable; + synchronized (mLock) { + if (!mDebuggableApps.containsKey(packageName)) { + try { + final ApplicationInfo appInfo = AppGlobals.getPackageManager() + .getApplicationInfo(pkg, 0, userId); + if (appInfo != null) { + mDebuggableApps.put(packageName, + (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0); + } else { + return JobScheduler.RESULT_FAILURE; + } + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + isDebuggable = mDebuggableApps.get(packageName); + } + if (isDebuggable) { + // Only throw the exception for debuggable apps. + throw new IllegalStateException( + "schedule()/enqueue() called more than " + + mQuotaTracker.getLimit(Category.SINGLE_CATEGORY) + + " times in the past " + + mQuotaTracker.getWindowSizeMs(Category.SINGLE_CATEGORY) + + "ms"); + } + } + return JobScheduler.RESULT_FAILURE; + } + mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG); + } + try { if (ActivityManager.getService().isAppStartModeDisabled(uId, job.getService().getPackageName())) { @@ -1296,6 +1423,12 @@ public class JobSchedulerService extends com.android.server.SystemService // Set up the app standby bucketing tracker mStandbyTracker = new StandbyTracker(); mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class); + mPlatformCompat = + (PlatformCompat) ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE); + mQuotaTracker = new CountQuotaTracker(context, Categorizer.SINGLE_CATEGORIZER); + mQuotaTracker.setCountLimit(Category.SINGLE_CATEGORY, + mConstants.API_QUOTA_SCHEDULE_COUNT, + mConstants.API_QUOTA_SCHEDULE_WINDOW_MS); AppStandbyInternal appStandby = LocalServices.getService(AppStandbyInternal.class); appStandby.addListener(mStandbyTracker); @@ -2745,7 +2878,7 @@ public class JobSchedulerService extends com.android.server.SystemService return new ParceledListSlice<>(snapshots); } } - }; + } // Shell command infrastructure: run the given job immediately int executeRunCommand(String pkgName, int userId, int jobId, boolean force) { @@ -2968,6 +3101,10 @@ public class JobSchedulerService extends com.android.server.SystemService return 0; } + void resetScheduleQuota() { + mQuotaTracker.clear(); + } + void triggerDockState(boolean idleState) { final Intent dockIntent; if (idleState) { @@ -3030,6 +3167,9 @@ public class JobSchedulerService extends com.android.server.SystemService } pw.println(); + mQuotaTracker.dump(pw); + pw.println(); + pw.println("Started users: " + Arrays.toString(mStartedUsers)); pw.print("Registered "); pw.print(mJobs.size()); @@ -3217,6 +3357,9 @@ public class JobSchedulerService extends com.android.server.SystemService for (int u : mStartedUsers) { proto.write(JobSchedulerServiceDumpProto.STARTED_USERS, u); } + + mQuotaTracker.dump(proto, JobSchedulerServiceDumpProto.QUOTA_TRACKER); + if (mJobs.size() > 0) { final List<JobStatus> jobs = mJobs.mJobSet.getAllJobs(); sortJobs(jobs); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java index a5c6c0132fc8..6becf04deb98 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java @@ -66,6 +66,8 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { return getJobState(pw); case "heartbeat": return doHeartbeat(pw); + case "reset-schedule-quota": + return resetScheduleQuota(pw); case "trigger-dock-state": return triggerDockState(pw); default: @@ -344,6 +346,18 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { return -1; } + private int resetScheduleQuota(PrintWriter pw) throws Exception { + checkPermission("reset schedule quota"); + + final long ident = Binder.clearCallingIdentity(); + try { + mInternal.resetScheduleQuota(); + } finally { + Binder.restoreCallingIdentity(ident); + } + return 0; + } + private int triggerDockState(PrintWriter pw) throws Exception { checkPermission("trigger wireless charging dock state"); diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java index 82292cfeea09..b9df30aa4d95 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java @@ -17,7 +17,7 @@ package com.android.server.usage; import static android.app.usage.UsageStatsManager.REASON_MAIN_DEFAULT; -import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED; +import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER; import static android.app.usage.UsageStatsManager.REASON_MAIN_MASK; import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED; import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE; @@ -441,7 +441,7 @@ public class AppIdleHistory { elapsedRealtime, true); if (idle) { appUsageHistory.currentBucket = STANDBY_BUCKET_RARE; - appUsageHistory.bucketingReason = REASON_MAIN_FORCED; + appUsageHistory.bucketingReason = REASON_MAIN_FORCED_BY_USER; } else { appUsageHistory.currentBucket = STANDBY_BUCKET_ACTIVE; // This is to pretend that the app was just used, don't freeze the state anymore. diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index 58eb58961ac4..eb0b54b1d9fc 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -17,7 +17,8 @@ package com.android.server.usage; import static android.app.usage.UsageStatsManager.REASON_MAIN_DEFAULT; -import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED; +import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_SYSTEM; +import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER; import static android.app.usage.UsageStatsManager.REASON_MAIN_MASK; import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED; import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT; @@ -565,7 +566,7 @@ public class AppStandbyController implements AppStandbyInternal { // If the bucket was forced by the user/developer, leave it alone. // A usage event will be the only way to bring it out of this forced state - if (oldMainReason == REASON_MAIN_FORCED) { + if (oldMainReason == REASON_MAIN_FORCED_BY_USER) { return; } final int oldBucket = app.currentBucket; @@ -783,7 +784,7 @@ public class AppStandbyController implements AppStandbyInternal { // Inform listeners if necessary if (previouslyIdle != stillIdle) { maybeInformListeners(packageName, userId, elapsedRealtime, standbyBucket, - REASON_MAIN_FORCED, false); + REASON_MAIN_FORCED_BY_USER, false); if (!stillIdle) { notifyBatteryStats(packageName, userId, idle); } @@ -1030,8 +1031,17 @@ public class AppStandbyController implements AppStandbyInternal { callingPid, callingUid, userId, false, true, "setAppStandbyBucket", null); final boolean shellCaller = callingUid == Process.ROOT_UID || callingUid == Process.SHELL_UID; - final boolean systemCaller = UserHandle.isCore(callingUid); - final int reason = systemCaller ? REASON_MAIN_FORCED : REASON_MAIN_PREDICTED; + final int reason; + // The Settings app runs in the system UID but in a separate process. Assume + // things coming from other processes are due to the user. + if ((UserHandle.isSameApp(callingUid, Process.SYSTEM_UID) && callingPid != Process.myPid()) + || shellCaller) { + reason = REASON_MAIN_FORCED_BY_USER; + } else if (UserHandle.isCore(callingUid)) { + reason = REASON_MAIN_FORCED_BY_SYSTEM; + } else { + reason = REASON_MAIN_PREDICTED; + } final int packageFlags = PackageManager.MATCH_ANY_USER | PackageManager.MATCH_DIRECT_BOOT_UNAWARE | PackageManager.MATCH_DIRECT_BOOT_AWARE; @@ -1087,7 +1097,11 @@ public class AppStandbyController implements AppStandbyInternal { } // If the bucket was forced, don't allow prediction to override - if ((app.bucketingReason & REASON_MAIN_MASK) == REASON_MAIN_FORCED && predicted) return; + if (predicted + && ((app.bucketingReason & REASON_MAIN_MASK) == REASON_MAIN_FORCED_BY_USER + || (app.bucketingReason & REASON_MAIN_MASK) == REASON_MAIN_FORCED_BY_SYSTEM)) { + return; + } // If the bucket is required to stay in a higher state for a specified duration, don't // override unless the duration has passed diff --git a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING new file mode 100644 index 000000000000..cf70878b8899 --- /dev/null +++ b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING @@ -0,0 +1,19 @@ +{ + "presubmit": [ + { + "name": "FrameworksServicesTests", + "options": [ + {"include-filter": "com.android.server.usage"}, + {"exclude-annotation": "androidx.test.filters.FlakyTest"} + ] + } + ], + "postsubmit": [ + { + "name": "FrameworksServicesTests", + "options": [ + {"include-filter": "com.android.server.usage"} + ] + } + ] +}
\ No newline at end of file diff --git a/apex/permission/Android.bp b/apex/permission/Android.bp index 5945fb381963..d746ea6af397 100644 --- a/apex/permission/Android.bp +++ b/apex/permission/Android.bp @@ -22,6 +22,11 @@ apex_defaults { name: "com.android.permission-defaults", key: "com.android.permission.key", certificate: ":com.android.permission.certificate", + java_libs: [ + "framework-permission", + "service-permission", + ], + apps: ["PermissionController"], } apex_key { diff --git a/apex/permission/framework/Android.bp b/apex/permission/framework/Android.bp new file mode 100644 index 000000000000..8b03da3a9530 --- /dev/null +++ b/apex/permission/framework/Android.bp @@ -0,0 +1,60 @@ +// 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. + +filegroup { + name: "framework-permission-sources", + srcs: [ + "java/**/*.java", + "java/**/*.aidl", + ], + path: "java", +} + +java_library { + name: "framework-permission", + srcs: [ + ":framework-permission-sources", + ], + sdk_version: "system_current", + apex_available: [ + "com.android.permission", + "test_com.android.permission", + ], + hostdex: true, + installable: true, + visibility: [ + "//frameworks/base/apex/permission:__subpackages__", + ], +} + +droidstubs { + name: "framework-permission-stubs-sources", + srcs: [ + ":framework-annotations", + ":framework-permission-sources", + ], + sdk_version: "system_current", + defaults: [ + "framework-module-stubs-defaults-systemapi", + ], +} + +java_library { + name: "framework-permission-stubs", + srcs: [ + ":framework-permission-stubs-sources", + ], + sdk_version: "system_current", + installable: false, +} diff --git a/apex/permission/framework/java/android/permission/PermissionState.java b/apex/permission/framework/java/android/permission/PermissionState.java new file mode 100644 index 000000000000..e810db8ecbfe --- /dev/null +++ b/apex/permission/framework/java/android/permission/PermissionState.java @@ -0,0 +1,22 @@ +/* + * 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.permission; + +/** + * @hide + */ +public class PermissionState {} diff --git a/apex/permission/service/Android.bp b/apex/permission/service/Android.bp new file mode 100644 index 000000000000..972b362509c0 --- /dev/null +++ b/apex/permission/service/Android.bp @@ -0,0 +1,29 @@ +// 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. + +java_library { + name: "service-permission", + srcs: [ + "java/**/*.java", + ], + sdk_version: "system_current", + libs: [ + "framework-permission", + ], + apex_available: [ + "com.android.permission", + "test_com.android.permission", + ], + installable: true, +} diff --git a/apex/permission/service/java/com/android/server/permission/RuntimePermissionPersistence.java b/apex/permission/service/java/com/android/server/permission/RuntimePermissionPersistence.java new file mode 100644 index 000000000000..a534e22c04cb --- /dev/null +++ b/apex/permission/service/java/com/android/server/permission/RuntimePermissionPersistence.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission; + +/** + * Persistence for runtime permissions. + */ +public class RuntimePermissionPersistence {} diff --git a/apex/statsd/aidl/android/os/IStatsd.aidl b/apex/statsd/aidl/android/os/IStatsd.aidl index c409f516de1b..0ecf2f0b4851 100644 --- a/apex/statsd/aidl/android/os/IStatsd.aidl +++ b/apex/statsd/aidl/android/os/IStatsd.aidl @@ -212,12 +212,17 @@ interface IStatsd { * * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS */ - oneway void unregisterPullerCallback(int atomTag, String packageName); + oneway void unregisterPullerCallback(int atomTag, String packageName); - /** - * Unregisters any pullAtomCallback for the given uid/atom. - */ - oneway void unregisterPullAtomCallback(int uid, int atomTag); + /** + * Unregisters any pullAtomCallback for the given uid/atom. + */ + oneway void unregisterPullAtomCallback(int uid, int atomTag); + + /** + * Unregisters any pullAtomCallback for the given atom. + */ + oneway void unregisterNativePullAtomCallback(int atomTag); /** * The install requires staging. diff --git a/apex/statsd/framework/java/android/util/StatsEvent.java b/apex/statsd/framework/java/android/util/StatsEvent.java index c7659457bdf9..1a45c4a5b7f6 100644 --- a/apex/statsd/framework/java/android/util/StatsEvent.java +++ b/apex/statsd/framework/java/android/util/StatsEvent.java @@ -20,6 +20,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.os.SystemClock; import com.android.internal.annotations.GuardedBy; @@ -51,6 +52,7 @@ import com.android.internal.annotations.VisibleForTesting; * </pre> * @hide **/ +@SystemApi public final class StatsEvent { // Type Ids. /** @@ -270,6 +272,8 @@ public final class StatsEvent { /** * Recycle resources used by this StatsEvent object. * No actions should be taken on this StatsEvent after release() is called. + * + * @hide **/ public void release() { if (mBuffer != null) { @@ -363,16 +367,6 @@ public final class StatsEvent { } /** - * Sets the timestamp in nanos for this StatsEvent. - **/ - @VisibleForTesting - @NonNull - public Builder setTimestampNs(final long timestampNs) { - mTimestampNs = timestampNs; - return this; - } - - /** * Write a boolean field to this StatsEvent. **/ @NonNull @@ -500,14 +494,14 @@ public final class StatsEvent { **/ @NonNull public Builder writeKeyValuePairs( - @NonNull final SparseIntArray intMap, - @NonNull final SparseLongArray longMap, - @NonNull final SparseArray<String> stringMap, - @NonNull final SparseArray<Float> floatMap) { - final int intMapSize = intMap.size(); - final int longMapSize = longMap.size(); - final int stringMapSize = stringMap.size(); - final int floatMapSize = floatMap.size(); + @Nullable final SparseIntArray intMap, + @Nullable final SparseLongArray longMap, + @Nullable final SparseArray<String> stringMap, + @Nullable final SparseArray<Float> floatMap) { + final int intMapSize = null == intMap ? 0 : intMap.size(); + final int longMapSize = null == longMap ? 0 : longMap.size(); + final int stringMapSize = null == stringMap ? 0 : stringMap.size(); + final int floatMapSize = null == floatMap ? 0 : floatMap.size(); final int totalCount = intMapSize + longMapSize + stringMapSize + floatMapSize; if (totalCount > MAX_KEY_VALUE_PAIRS) { diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java index 0f981e25b37a..17573bb22fea 100644 --- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java +++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java @@ -24,11 +24,11 @@ import static android.os.storage.VolumeInfo.TYPE_PRIVATE; import static android.os.storage.VolumeInfo.TYPE_PUBLIC; import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem; -import static com.android.server.stats.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs; -import static com.android.server.stats.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs; -import static com.android.server.stats.ProcfsMemoryUtil.forEachPid; -import static com.android.server.stats.ProcfsMemoryUtil.readCmdlineFromProcfs; -import static com.android.server.stats.ProcfsMemoryUtil.readMemorySnapshotFromProcfs; +import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs; +import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs; +import static com.android.server.stats.pull.ProcfsMemoryUtil.forEachPid; +import static com.android.server.stats.pull.ProcfsMemoryUtil.readCmdlineFromProcfs; +import static com.android.server.stats.pull.ProcfsMemoryUtil.readMemorySnapshotFromProcfs; import android.annotation.NonNull; import android.annotation.Nullable; @@ -135,8 +135,8 @@ import com.android.server.SystemServiceManager; import com.android.server.am.MemoryStatUtil.MemoryStat; import com.android.server.notification.NotificationManagerService; import com.android.server.role.RoleManagerInternal; -import com.android.server.stats.IonMemoryUtil.IonAllocations; -import com.android.server.stats.ProcfsMemoryUtil.MemorySnapshot; +import com.android.server.stats.pull.IonMemoryUtil.IonAllocations; +import com.android.server.stats.pull.ProcfsMemoryUtil.MemorySnapshot; import com.android.server.storage.DiskStatsFileLogger; import com.android.server.storage.DiskStatsLoggingService; @@ -714,371 +714,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } - private void addNetworkStats( - int tag, List<StatsLogEventWrapper> ret, NetworkStats stats, boolean withFGBG) { - int size = stats.size(); - long elapsedNanos = SystemClock.elapsedRealtimeNanos(); - long wallClockNanos = SystemClock.currentTimeMicro() * 1000L; - NetworkStats.Entry entry = new NetworkStats.Entry(); // For recycling - for (int j = 0; j < size; j++) { - stats.getValues(j, entry); - StatsLogEventWrapper e = new StatsLogEventWrapper(tag, elapsedNanos, wallClockNanos); - e.writeInt(entry.uid); - if (withFGBG) { - e.writeInt(entry.set); - } - e.writeLong(entry.rxBytes); - e.writeLong(entry.rxPackets); - e.writeLong(entry.txBytes); - e.writeLong(entry.txPackets); - ret.add(e); - } - } - - /** - * Allows rollups per UID but keeping the set (foreground/background) slicing. - * Adapted from groupedByUid in frameworks/base/core/java/android/net/NetworkStats.java - */ - private NetworkStats rollupNetworkStatsByFGBG(NetworkStats stats) { - final NetworkStats ret = new NetworkStats(stats.getElapsedRealtime(), 1); - - final NetworkStats.Entry entry = new NetworkStats.Entry(); - entry.iface = NetworkStats.IFACE_ALL; - entry.tag = NetworkStats.TAG_NONE; - entry.metered = NetworkStats.METERED_ALL; - entry.roaming = NetworkStats.ROAMING_ALL; - - int size = stats.size(); - NetworkStats.Entry recycle = new NetworkStats.Entry(); // Used for retrieving values - for (int i = 0; i < size; i++) { - stats.getValues(i, recycle); - - // Skip specific tags, since already counted in TAG_NONE - if (recycle.tag != NetworkStats.TAG_NONE) continue; - - entry.set = recycle.set; // Allows slicing by background/foreground - entry.uid = recycle.uid; - entry.rxBytes = recycle.rxBytes; - entry.rxPackets = recycle.rxPackets; - entry.txBytes = recycle.txBytes; - entry.txPackets = recycle.txPackets; - // Operations purposefully omitted since we don't use them for statsd. - ret.combineValues(entry); - } - return ret; - } - - /** - * Helper method to extract the Parcelable controller info from a - * SynchronousResultReceiver. - */ - private static <T extends Parcelable> T awaitControllerInfo( - @Nullable SynchronousResultReceiver receiver) { - if (receiver == null) { - return null; - } - - try { - final SynchronousResultReceiver.Result result = - receiver.awaitResult(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS); - if (result.bundle != null) { - // This is the final destination for the Bundle. - result.bundle.setDefusable(true); - - final T data = result.bundle.getParcelable( - RESULT_RECEIVER_CONTROLLER_KEY); - if (data != null) { - return data; - } - } - Slog.e(TAG, "no controller energy info supplied for " + receiver.getName()); - } catch (TimeoutException e) { - Slog.w(TAG, "timeout reading " + receiver.getName() + " stats"); - } - return null; - } - - private void pullKernelWakelock( - int tagId, long elapsedNanos, long wallClockNanos, - List<StatsLogEventWrapper> pulledData) { - final KernelWakelockStats wakelockStats = - mKernelWakelockReader.readKernelWakelockStats(mTmpWakelockStats); - for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) { - String name = ent.getKey(); - KernelWakelockStats.Entry kws = ent.getValue(); - StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); - e.writeString(name); - e.writeInt(kws.mCount); - e.writeInt(kws.mVersion); - e.writeLong(kws.mTotalTime); - pulledData.add(e); - } - } - - private void pullWifiBytesTransferByFgBg( - int tagId, long elapsedNanos, long wallClockNanos, - List<StatsLogEventWrapper> pulledData) { - long token = Binder.clearCallingIdentity(); - try { - BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class); - String[] ifaces = bs.getWifiIfaces(); - if (ifaces.length == 0) { - return; - } - if (mNetworkStatsService == null) { - Slog.e(TAG, "NetworkStats Service is not available!"); - return; - } - NetworkStats stats = rollupNetworkStatsByFGBG( - mNetworkStatsService.getDetailedUidStats(ifaces)); - addNetworkStats(tagId, pulledData, stats, true); - } catch (RemoteException e) { - Slog.e(TAG, "Pulling netstats for wifi bytes w/ fg/bg has error", e); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - private void pullMobileBytesTransfer( - int tagId, long elapsedNanos, long wallClockNanos, - List<StatsLogEventWrapper> pulledData) { - long token = Binder.clearCallingIdentity(); - try { - BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class); - String[] ifaces = bs.getMobileIfaces(); - if (ifaces.length == 0) { - return; - } - if (mNetworkStatsService == null) { - Slog.e(TAG, "NetworkStats Service is not available!"); - return; - } - // Combine all the metrics per Uid into one record. - NetworkStats stats = mNetworkStatsService.getDetailedUidStats(ifaces).groupedByUid(); - addNetworkStats(tagId, pulledData, stats, false); - } catch (RemoteException e) { - Slog.e(TAG, "Pulling netstats for mobile bytes has error", e); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - private void pullBluetoothBytesTransfer( - int tagId, long elapsedNanos, long wallClockNanos, - List<StatsLogEventWrapper> pulledData) { - BluetoothActivityEnergyInfo info = fetchBluetoothData(); - if (info.getUidTraffic() != null) { - for (UidTraffic traffic : info.getUidTraffic()) { - StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, - wallClockNanos); - e.writeInt(traffic.getUid()); - e.writeLong(traffic.getRxBytes()); - e.writeLong(traffic.getTxBytes()); - pulledData.add(e); - } - } - } - - private void pullMobileBytesTransferByFgBg( - int tagId, long elapsedNanos, long wallClockNanos, - List<StatsLogEventWrapper> pulledData) { - long token = Binder.clearCallingIdentity(); - try { - BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class); - String[] ifaces = bs.getMobileIfaces(); - if (ifaces.length == 0) { - return; - } - if (mNetworkStatsService == null) { - Slog.e(TAG, "NetworkStats Service is not available!"); - return; - } - NetworkStats stats = rollupNetworkStatsByFGBG( - mNetworkStatsService.getDetailedUidStats(ifaces)); - addNetworkStats(tagId, pulledData, stats, true); - } catch (RemoteException e) { - Slog.e(TAG, "Pulling netstats for mobile bytes w/ fg/bg has error", e); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - private void pullCpuTimePerFreq( - int tagId, long elapsedNanos, long wallClockNanos, - List<StatsLogEventWrapper> pulledData) { - for (int cluster = 0; cluster < mKernelCpuSpeedReaders.length; cluster++) { - long[] clusterTimeMs = mKernelCpuSpeedReaders[cluster].readAbsolute(); - if (clusterTimeMs != null) { - for (int speed = clusterTimeMs.length - 1; speed >= 0; --speed) { - StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, - wallClockNanos); - e.writeInt(cluster); - e.writeInt(speed); - e.writeLong(clusterTimeMs[speed]); - pulledData.add(e); - } - } - } - } - - private void pullKernelUidCpuTime( - int tagId, long elapsedNanos, long wallClockNanos, - List<StatsLogEventWrapper> pulledData) { - mCpuUidUserSysTimeReader.readAbsolute((uid, timesUs) -> { - long userTimeUs = timesUs[0], systemTimeUs = timesUs[1]; - StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); - e.writeInt(uid); - e.writeLong(userTimeUs); - e.writeLong(systemTimeUs); - pulledData.add(e); - }); - } - - private void pullKernelUidCpuFreqTime( - int tagId, long elapsedNanos, long wallClockNanos, - List<StatsLogEventWrapper> pulledData) { - mCpuUidFreqTimeReader.readAbsolute((uid, cpuFreqTimeMs) -> { - for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) { - if (cpuFreqTimeMs[freqIndex] != 0) { - StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, - wallClockNanos); - e.writeInt(uid); - e.writeInt(freqIndex); - e.writeLong(cpuFreqTimeMs[freqIndex]); - pulledData.add(e); - } - } - }); - } - - private void pullKernelUidCpuClusterTime( - int tagId, long elapsedNanos, long wallClockNanos, - List<StatsLogEventWrapper> pulledData) { - mCpuUidClusterTimeReader.readAbsolute((uid, cpuClusterTimesMs) -> { - for (int i = 0; i < cpuClusterTimesMs.length; i++) { - StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, - wallClockNanos); - e.writeInt(uid); - e.writeInt(i); - e.writeLong(cpuClusterTimesMs[i]); - pulledData.add(e); - } - }); - } - - private void pullKernelUidCpuActiveTime( - int tagId, long elapsedNanos, long wallClockNanos, - List<StatsLogEventWrapper> pulledData) { - mCpuUidActiveTimeReader.readAbsolute((uid, cpuActiveTimesMs) -> { - StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); - e.writeInt(uid); - e.writeLong((long) cpuActiveTimesMs); - pulledData.add(e); - }); - } - - private void pullWifiActivityInfo( - int tagId, long elapsedNanos, long wallClockNanos, - List<StatsLogEventWrapper> pulledData) { - WifiManager wifiManager; - synchronized (this) { - if (mWifiManager == null) { - mWifiManager = mContext.getSystemService(WifiManager.class); - } - wifiManager = mWifiManager; - } - if (wifiManager == null) { - return; - } - long token = Binder.clearCallingIdentity(); - try { - SynchronousResultReceiver wifiReceiver = new SynchronousResultReceiver("wifi"); - wifiManager.getWifiActivityEnergyInfoAsync( - new Executor() { - @Override - public void execute(Runnable runnable) { - // run the listener on the binder thread, if it was run on the main - // thread it would deadlock since we would be waiting on ourselves - runnable.run(); - } - }, - info -> { - Bundle bundle = new Bundle(); - bundle.putParcelable(BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY, info); - wifiReceiver.send(0, bundle); - } - ); - final WifiActivityEnergyInfo wifiInfo = awaitControllerInfo(wifiReceiver); - if (wifiInfo == null) { - return; - } - StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); - e.writeLong(wifiInfo.getTimeSinceBootMillis()); - e.writeInt(wifiInfo.getStackState()); - e.writeLong(wifiInfo.getControllerTxDurationMillis()); - e.writeLong(wifiInfo.getControllerRxDurationMillis()); - e.writeLong(wifiInfo.getControllerIdleDurationMillis()); - e.writeLong(wifiInfo.getControllerEnergyUsedMicroJoules()); - pulledData.add(e); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - private void pullModemActivityInfo( - int tagId, long elapsedNanos, long wallClockNanos, - List<StatsLogEventWrapper> pulledData) { - long token = Binder.clearCallingIdentity(); - synchronized (this) { - if (mTelephony == null) { - mTelephony = mContext.getSystemService(TelephonyManager.class); - } - } - if (mTelephony != null) { - SynchronousResultReceiver modemReceiver = new SynchronousResultReceiver("telephony"); - mTelephony.requestModemActivityInfo(modemReceiver); - final ModemActivityInfo modemInfo = awaitControllerInfo(modemReceiver); - StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); - e.writeLong(modemInfo.getTimestamp()); - e.writeLong(modemInfo.getSleepTimeMillis()); - e.writeLong(modemInfo.getIdleTimeMillis()); - e.writeLong(modemInfo.getTransmitPowerInfo().get(0).getTimeInMillis()); - e.writeLong(modemInfo.getTransmitPowerInfo().get(1).getTimeInMillis()); - e.writeLong(modemInfo.getTransmitPowerInfo().get(2).getTimeInMillis()); - e.writeLong(modemInfo.getTransmitPowerInfo().get(3).getTimeInMillis()); - e.writeLong(modemInfo.getTransmitPowerInfo().get(4).getTimeInMillis()); - e.writeLong(modemInfo.getReceiveTimeMillis()); - pulledData.add(e); - } - } - - private void pullBluetoothActivityInfo( - int tagId, long elapsedNanos, long wallClockNanos, - List<StatsLogEventWrapper> pulledData) { - BluetoothActivityEnergyInfo info = fetchBluetoothData(); - StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); - e.writeLong(info.getTimeStamp()); - e.writeInt(info.getBluetoothStackState()); - e.writeLong(info.getControllerTxTimeMillis()); - e.writeLong(info.getControllerRxTimeMillis()); - e.writeLong(info.getControllerIdleTimeMillis()); - e.writeLong(info.getControllerEnergyUsed()); - pulledData.add(e); - } - - private synchronized BluetoothActivityEnergyInfo fetchBluetoothData() { - final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - if (adapter != null) { - SynchronousResultReceiver bluetoothReceiver = new SynchronousResultReceiver( - "bluetooth"); - adapter.requestControllerActivityEnergyInfo(bluetoothReceiver); - return awaitControllerInfo(bluetoothReceiver); - } else { - Slog.e(TAG, "Failed to get bluetooth adapter!"); - return null; - } - } - private void pullSystemElapsedRealtime( int tagId, long elapsedNanos, long wallClockNanos, List<StatsLogEventWrapper> pulledData) { @@ -1087,214 +722,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { pulledData.add(e); } - private void pullSystemUpTime(int tagId, long elapsedNanos, long wallClockNanos, - List<StatsLogEventWrapper> pulledData) { - StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); - e.writeLong(SystemClock.uptimeMillis()); - pulledData.add(e); - } - - private void pullProcessMemoryState( - int tagId, long elapsedNanos, long wallClockNanos, - List<StatsLogEventWrapper> pulledData) { - List<ProcessMemoryState> processMemoryStates = - LocalServices.getService( - ActivityManagerInternal.class).getMemoryStateForProcesses(); - for (ProcessMemoryState processMemoryState : processMemoryStates) { - final MemoryStat memoryStat = readMemoryStatFromFilesystem(processMemoryState.uid, - processMemoryState.pid); - if (memoryStat == null) { - continue; - } - StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); - e.writeInt(processMemoryState.uid); - e.writeString(processMemoryState.processName); - e.writeInt(processMemoryState.oomScore); - e.writeLong(memoryStat.pgfault); - e.writeLong(memoryStat.pgmajfault); - e.writeLong(memoryStat.rssInBytes); - e.writeLong(memoryStat.cacheInBytes); - e.writeLong(memoryStat.swapInBytes); - e.writeLong(-1); // unused - e.writeLong(-1); // unused - e.writeInt(-1); // unsed - pulledData.add(e); - } - } - - private void pullProcessMemoryHighWaterMark( - int tagId, long elapsedNanos, long wallClockNanos, - List<StatsLogEventWrapper> pulledData) { - List<ProcessMemoryState> managedProcessList = - LocalServices.getService( - ActivityManagerInternal.class).getMemoryStateForProcesses(); - for (ProcessMemoryState managedProcess : managedProcessList) { - final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(managedProcess.pid); - if (snapshot == null) { - continue; - } - StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); - e.writeInt(managedProcess.uid); - e.writeString(managedProcess.processName); - // RSS high-water mark in bytes. - e.writeLong((long) snapshot.rssHighWaterMarkInKilobytes * 1024L); - e.writeInt(snapshot.rssHighWaterMarkInKilobytes); - pulledData.add(e); - } - forEachPid((pid, cmdLine) -> { - if (!MEMORY_INTERESTING_NATIVE_PROCESSES.contains(cmdLine)) { - return; - } - final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(pid); - if (snapshot == null) { - return; - } - // Sometimes we get here a process that is not included in the whitelist. It comes - // from forking the zygote for an app. We can ignore that sample because this process - // is collected by ProcessMemoryState. - if (isAppUid(snapshot.uid)) { - return; - } - StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); - e.writeInt(snapshot.uid); - e.writeString(cmdLine); - // RSS high-water mark in bytes. - e.writeLong((long) snapshot.rssHighWaterMarkInKilobytes * 1024L); - e.writeInt(snapshot.rssHighWaterMarkInKilobytes); - pulledData.add(e); - }); - // Invoke rss_hwm_reset binary to reset RSS HWM counters for all processes. - SystemProperties.set("sys.rss_hwm_reset.on", "1"); - } - - private void pullProcessMemorySnapshot( - int tagId, long elapsedNanos, long wallClockNanos, - List<StatsLogEventWrapper> pulledData) { - List<ProcessMemoryState> managedProcessList = - LocalServices.getService( - ActivityManagerInternal.class).getMemoryStateForProcesses(); - for (ProcessMemoryState managedProcess : managedProcessList) { - final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(managedProcess.pid); - if (snapshot == null) { - continue; - } - StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); - e.writeInt(managedProcess.uid); - e.writeString(managedProcess.processName); - e.writeInt(managedProcess.pid); - e.writeInt(managedProcess.oomScore); - e.writeInt(snapshot.rssInKilobytes); - e.writeInt(snapshot.anonRssInKilobytes); - e.writeInt(snapshot.swapInKilobytes); - e.writeInt(snapshot.anonRssInKilobytes + snapshot.swapInKilobytes); - pulledData.add(e); - } - forEachPid((pid, cmdLine) -> { - if (!MEMORY_INTERESTING_NATIVE_PROCESSES.contains(cmdLine)) { - return; - } - final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(pid); - if (snapshot == null) { - return; - } - // Sometimes we get here a process that is not included in the whitelist. It comes - // from forking the zygote for an app. We can ignore that sample because this process - // is collected by ProcessMemoryState. - if (isAppUid(snapshot.uid)) { - return; - } - StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); - e.writeInt(snapshot.uid); - e.writeString(cmdLine); - e.writeInt(pid); - e.writeInt(-1001); // Placeholder for native processes, OOM_SCORE_ADJ_MIN - 1. - e.writeInt(snapshot.rssInKilobytes); - e.writeInt(snapshot.anonRssInKilobytes); - e.writeInt(snapshot.swapInKilobytes); - e.writeInt(snapshot.anonRssInKilobytes + snapshot.swapInKilobytes); - pulledData.add(e); - }); - } - - private static boolean isAppUid(int uid) { - return uid >= MIN_APP_UID; - } - - private void pullSystemIonHeapSize( - int tagId, long elapsedNanos, long wallClockNanos, - List<StatsLogEventWrapper> pulledData) { - final long systemIonHeapSizeInBytes = readSystemIonHeapSizeFromDebugfs(); - StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); - e.writeLong(systemIonHeapSizeInBytes); - pulledData.add(e); - } - - private void pullProcessSystemIonHeapSize( - int tagId, long elapsedNanos, long wallClockNanos, - List<StatsLogEventWrapper> pulledData) { - List<IonAllocations> result = readProcessSystemIonHeapSizesFromDebugfs(); - for (IonAllocations allocations : result) { - StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); - e.writeInt(getUidForPid(allocations.pid)); - e.writeString(readCmdlineFromProcfs(allocations.pid)); - e.writeInt((int) (allocations.totalSizeInBytes / 1024)); - e.writeInt(allocations.count); - e.writeInt((int) (allocations.maxSizeInBytes / 1024)); - pulledData.add(e); - } - } - - private void pullBinderCallsStats( - int tagId, long elapsedNanos, long wallClockNanos, - List<StatsLogEventWrapper> pulledData) { - BinderCallsStatsService.Internal binderStats = - LocalServices.getService(BinderCallsStatsService.Internal.class); - if (binderStats == null) { - throw new IllegalStateException("binderStats is null"); - } - - List<ExportedCallStat> callStats = binderStats.getExportedCallStats(); - binderStats.reset(); - for (ExportedCallStat callStat : callStats) { - StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); - e.writeInt(callStat.workSourceUid); - e.writeString(callStat.className); - e.writeString(callStat.methodName); - e.writeLong(callStat.callCount); - e.writeLong(callStat.exceptionCount); - e.writeLong(callStat.latencyMicros); - e.writeLong(callStat.maxLatencyMicros); - e.writeLong(callStat.cpuTimeMicros); - e.writeLong(callStat.maxCpuTimeMicros); - e.writeLong(callStat.maxReplySizeBytes); - e.writeLong(callStat.maxRequestSizeBytes); - e.writeLong(callStat.recordedCallCount); - e.writeInt(callStat.screenInteractive ? 1 : 0); - e.writeInt(callStat.callingUid); - pulledData.add(e); - } - } - - private void pullBinderCallsStatsExceptions( - int tagId, long elapsedNanos, long wallClockNanos, - List<StatsLogEventWrapper> pulledData) { - BinderCallsStatsService.Internal binderStats = - LocalServices.getService(BinderCallsStatsService.Internal.class); - if (binderStats == null) { - throw new IllegalStateException("binderStats is null"); - } - - ArrayMap<String, Integer> exceptionStats = binderStats.getExportedExceptionStats(); - // TODO: decouple binder calls exceptions with the rest of the binder calls data so that we - // can reset the exception stats. - for (Entry<String, Integer> entry : exceptionStats.entrySet()) { - StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); - e.writeString(entry.getKey()); - e.writeInt(entry.getValue()); - pulledData.add(e); - } - } - private void pullLooperStats(int tagId, long elapsedNanos, long wallClockNanos, List<StatsLogEventWrapper> pulledData) { LooperStats looperStats = LocalServices.getService(LooperStats.class); @@ -1671,108 +1098,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } - private void pullPowerProfile( - int tagId, long elapsedNanos, long wallClockNanos, - List<StatsLogEventWrapper> pulledData) { - PowerProfile powerProfile = new PowerProfile(mContext); - Objects.requireNonNull(powerProfile); - - StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, - wallClockNanos); - ProtoOutputStream proto = new ProtoOutputStream(); - powerProfile.dumpDebug(proto); - proto.flush(); - e.writeStorage(proto.getBytes()); - pulledData.add(e); - } - - private void pullBuildInformation(int tagId, - long elapsedNanos, long wallClockNanos, List<StatsLogEventWrapper> pulledData) { - StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); - e.writeString(Build.FINGERPRINT); - e.writeString(Build.BRAND); - e.writeString(Build.PRODUCT); - e.writeString(Build.DEVICE); - e.writeString(Build.VERSION.RELEASE); - e.writeString(Build.ID); - e.writeString(Build.VERSION.INCREMENTAL); - e.writeString(Build.TYPE); - e.writeString(Build.TAGS); - pulledData.add(e); - } - - private BatteryStatsHelper getBatteryStatsHelper() { - if (mBatteryStatsHelper == null) { - final long callingToken = Binder.clearCallingIdentity(); - try { - // clearCallingIdentity required for BatteryStatsHelper.checkWifiOnly(). - mBatteryStatsHelper = new BatteryStatsHelper(mContext, false); - } finally { - Binder.restoreCallingIdentity(callingToken); - } - mBatteryStatsHelper.create((Bundle) null); - } - long currentTime = SystemClock.elapsedRealtime(); - if (currentTime - mBatteryStatsHelperTimestampMs >= MAX_BATTERY_STATS_HELPER_FREQUENCY_MS) { - // Load BatteryStats and do all the calculations. - mBatteryStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.USER_ALL); - // Calculations are done so we don't need to save the raw BatteryStats data in RAM. - mBatteryStatsHelper.clearStats(); - mBatteryStatsHelperTimestampMs = currentTime; - } - return mBatteryStatsHelper; - } - - private long milliAmpHrsToNanoAmpSecs(double mAh) { - final long MILLI_AMP_HR_TO_NANO_AMP_SECS = 1_000_000L * 3600L; - return (long) (mAh * MILLI_AMP_HR_TO_NANO_AMP_SECS + 0.5); - } - - private void pullDeviceCalculatedPowerUse(int tagId, - long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) { - BatteryStatsHelper bsHelper = getBatteryStatsHelper(); - StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); - e.writeLong(milliAmpHrsToNanoAmpSecs(bsHelper.getComputedPower())); - pulledData.add(e); - } - - private void pullDeviceCalculatedPowerBlameUid(int tagId, - long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) { - final List<BatterySipper> sippers = getBatteryStatsHelper().getUsageList(); - if (sippers == null) { - return; - } - for (BatterySipper bs : sippers) { - if (bs.drainType != bs.drainType.APP) { - continue; - } - StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); - e.writeInt(bs.uidObj.getUid()); - e.writeLong(milliAmpHrsToNanoAmpSecs(bs.totalPowerMah)); - pulledData.add(e); - } - } - - private void pullDeviceCalculatedPowerBlameOther(int tagId, - long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) { - final List<BatterySipper> sippers = getBatteryStatsHelper().getUsageList(); - if (sippers == null) { - return; - } - for (BatterySipper bs : sippers) { - if (bs.drainType == bs.drainType.APP) { - continue; // This is a separate atom; see pullDeviceCalculatedPowerBlameUid(). - } - if (bs.drainType == bs.drainType.USER) { - continue; // This is not supported. We purposefully calculate over USER_ALL. - } - StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); - e.writeInt(bs.drainType.ordinal()); - e.writeLong(milliAmpHrsToNanoAmpSecs(bs.totalPowerMah)); - pulledData.add(e); - } - } - private void pullDiskIo(int tagId, long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) { mStoragedUidIoStatsReader.readAbsolute((uid, fgCharsRead, fgCharsWrite, fgBytesRead, @@ -1871,49 +1196,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } - private void pullTemperature(int tagId, long elapsedNanos, long wallClockNanos, - List<StatsLogEventWrapper> pulledData) { - long callingToken = Binder.clearCallingIdentity(); - try { - List<Temperature> temperatures = sThermalService.getCurrentTemperatures(); - for (Temperature temp : temperatures) { - StatsLogEventWrapper e = - new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); - e.writeInt(temp.getType()); - e.writeString(temp.getName()); - e.writeInt((int) (temp.getValue() * 10)); - e.writeInt(temp.getStatus()); - pulledData.add(e); - } - } catch (RemoteException e) { - // Should not happen. - Slog.e(TAG, "Disconnected from thermal service. Cannot pull temperatures."); - } finally { - Binder.restoreCallingIdentity(callingToken); - } - } - - private void pullCoolingDevices(int tagId, long elapsedNanos, long wallClockNanos, - List<StatsLogEventWrapper> pulledData) { - long callingToken = Binder.clearCallingIdentity(); - try { - List<CoolingDevice> devices = sThermalService.getCurrentCoolingDevices(); - for (CoolingDevice device : devices) { - StatsLogEventWrapper e = - new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); - e.writeInt(device.getType()); - e.writeString(device.getName()); - e.writeInt((int) (device.getValue())); - pulledData.add(e); - } - } catch (RemoteException e) { - // Should not happen. - Slog.e(TAG, "Disconnected from thermal service. Cannot pull temperatures."); - } finally { - Binder.restoreCallingIdentity(callingToken); - } - } - private void pullDebugElapsedClock(int tagId, long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) { final long elapsedMillis = SystemClock.elapsedRealtime(); @@ -2291,117 +1573,12 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { long elapsedNanos = SystemClock.elapsedRealtimeNanos(); long wallClockNanos = SystemClock.currentTimeMicro() * 1000L; switch (tagId) { - - case StatsLog.MOBILE_BYTES_TRANSFER: { - pullMobileBytesTransfer(tagId, elapsedNanos, wallClockNanos, ret); - break; - } - - case StatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG: { - pullWifiBytesTransferByFgBg(tagId, elapsedNanos, wallClockNanos, ret); - break; - } - - case StatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG: { - pullMobileBytesTransferByFgBg(tagId, elapsedNanos, wallClockNanos, ret); - break; - } - - case StatsLog.BLUETOOTH_BYTES_TRANSFER: { - pullBluetoothBytesTransfer(tagId, elapsedNanos, wallClockNanos, ret); - break; - } - - case StatsLog.KERNEL_WAKELOCK: { - pullKernelWakelock(tagId, elapsedNanos, wallClockNanos, ret); - break; - } - - case StatsLog.CPU_TIME_PER_FREQ: { - pullCpuTimePerFreq(tagId, elapsedNanos, wallClockNanos, ret); - break; - } - - case StatsLog.CPU_TIME_PER_UID: { - pullKernelUidCpuTime(tagId, elapsedNanos, wallClockNanos, ret); - break; - } - - case StatsLog.CPU_TIME_PER_UID_FREQ: { - pullKernelUidCpuFreqTime(tagId, elapsedNanos, wallClockNanos, ret); - break; - } - - case StatsLog.CPU_CLUSTER_TIME: { - pullKernelUidCpuClusterTime(tagId, elapsedNanos, wallClockNanos, ret); - break; - } - - case StatsLog.CPU_ACTIVE_TIME: { - pullKernelUidCpuActiveTime(tagId, elapsedNanos, wallClockNanos, ret); - break; - } - - case StatsLog.WIFI_ACTIVITY_INFO: { - pullWifiActivityInfo(tagId, elapsedNanos, wallClockNanos, ret); - break; - } - - case StatsLog.MODEM_ACTIVITY_INFO: { - pullModemActivityInfo(tagId, elapsedNanos, wallClockNanos, ret); - break; - } - - case StatsLog.BLUETOOTH_ACTIVITY_INFO: { - pullBluetoothActivityInfo(tagId, elapsedNanos, wallClockNanos, ret); - break; - } - - case StatsLog.SYSTEM_UPTIME: { - pullSystemUpTime(tagId, elapsedNanos, wallClockNanos, ret); - break; - } case StatsLog.SYSTEM_ELAPSED_REALTIME: { pullSystemElapsedRealtime(tagId, elapsedNanos, wallClockNanos, ret); break; } - case StatsLog.PROCESS_MEMORY_STATE: { - pullProcessMemoryState(tagId, elapsedNanos, wallClockNanos, ret); - break; - } - - case StatsLog.PROCESS_MEMORY_HIGH_WATER_MARK: { - pullProcessMemoryHighWaterMark(tagId, elapsedNanos, wallClockNanos, ret); - break; - } - - case StatsLog.PROCESS_MEMORY_SNAPSHOT: { - pullProcessMemorySnapshot(tagId, elapsedNanos, wallClockNanos, ret); - break; - } - - case StatsLog.SYSTEM_ION_HEAP_SIZE: { - pullSystemIonHeapSize(tagId, elapsedNanos, wallClockNanos, ret); - break; - } - - case StatsLog.PROCESS_SYSTEM_ION_HEAP_SIZE: { - pullProcessSystemIonHeapSize(tagId, elapsedNanos, wallClockNanos, ret); - break; - } - - case StatsLog.BINDER_CALLS: { - pullBinderCallsStats(tagId, elapsedNanos, wallClockNanos, ret); - break; - } - - case StatsLog.BINDER_CALLS_EXCEPTIONS: { - pullBinderCallsStatsExceptions(tagId, elapsedNanos, wallClockNanos, ret); - break; - } - case StatsLog.LOOPER_STATS: { pullLooperStats(tagId, elapsedNanos, wallClockNanos, ret); break; @@ -2455,16 +1632,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { break; } - case StatsLog.POWER_PROFILE: { - pullPowerProfile(tagId, elapsedNanos, wallClockNanos, ret); - break; - } - - case StatsLog.BUILD_INFORMATION: { - pullBuildInformation(tagId, elapsedNanos, wallClockNanos, ret); - break; - } - case StatsLog.PROCESS_CPU_TIME: { pullProcessCpuTime(tagId, elapsedNanos, wallClockNanos, ret); break; @@ -2474,31 +1641,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { break; } - case StatsLog.DEVICE_CALCULATED_POWER_USE: { - pullDeviceCalculatedPowerUse(tagId, elapsedNanos, wallClockNanos, ret); - break; - } - - case StatsLog.DEVICE_CALCULATED_POWER_BLAME_UID: { - pullDeviceCalculatedPowerBlameUid(tagId, elapsedNanos, wallClockNanos, ret); - break; - } - - case StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER: { - pullDeviceCalculatedPowerBlameOther(tagId, elapsedNanos, wallClockNanos, ret); - break; - } - - case StatsLog.TEMPERATURE: { - pullTemperature(tagId, elapsedNanos, wallClockNanos, ret); - break; - } - - case StatsLog.COOLING_DEVICE: { - pullCoolingDevices(tagId, elapsedNanos, wallClockNanos, ret); - break; - } - case StatsLog.DEBUG_ELAPSED_CLOCK: { pullDebugElapsedClock(tagId, elapsedNanos, wallClockNanos, ret); break; diff --git a/api/current.txt b/api/current.txt index 00a58157c50f..e27c318c09b9 100644 --- a/api/current.txt +++ b/api/current.txt @@ -386,6 +386,7 @@ package android { field public static final int canRequestFingerprintGestures = 16844109; // 0x101054d field public static final int canRequestTouchExplorationMode = 16843735; // 0x10103d7 field public static final int canRetrieveWindowContent = 16843653; // 0x1010385 + field public static final int canTakeScreenshot = 16844304; // 0x1010610 field public static final int candidatesTextStyleSpans = 16843312; // 0x1010230 field public static final int cantSaveState = 16844142; // 0x101056e field @Deprecated public static final int capitalize = 16843113; // 0x1010169 @@ -2864,6 +2865,7 @@ package android.accessibilityservice { method protected void onServiceConnected(); method public final boolean performGlobalAction(int); method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo); + method public boolean takeScreenshot(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.graphics.Bitmap>); field public static final int GESTURE_SWIPE_DOWN = 2; // 0x2 field public static final int GESTURE_SWIPE_DOWN_AND_LEFT = 15; // 0xf field public static final int GESTURE_SWIPE_DOWN_AND_RIGHT = 16; // 0x10 @@ -2960,6 +2962,7 @@ package android.accessibilityservice { field public static final int CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES = 64; // 0x40 field public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 2; // 0x2 field public static final int CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT = 1; // 0x1 + field public static final int CAPABILITY_CAN_TAKE_SCREENSHOT = 128; // 0x80 field @NonNull public static final android.os.Parcelable.Creator<android.accessibilityservice.AccessibilityServiceInfo> CREATOR; field public static final int DEFAULT = 1; // 0x1 field public static final int FEEDBACK_ALL_MASK = -1; // 0xffffffff @@ -6836,6 +6839,7 @@ package android.app.admin { method public boolean isManagedProfile(@NonNull android.content.ComponentName); method public boolean isMasterVolumeMuted(@NonNull android.content.ComponentName); method public boolean isNetworkLoggingEnabled(@Nullable android.content.ComponentName); + method public boolean isOrganizationOwnedDeviceWithManagedProfile(); method public boolean isOverrideApnEnabled(@NonNull android.content.ComponentName); method public boolean isPackageSuspended(@NonNull android.content.ComponentName, String) throws android.content.pm.PackageManager.NameNotFoundException; method public boolean isProfileOwnerApp(String); @@ -10401,6 +10405,7 @@ package android.content { field public static final String ACTION_CLOSE_SYSTEM_DIALOGS = "android.intent.action.CLOSE_SYSTEM_DIALOGS"; field public static final String ACTION_CONFIGURATION_CHANGED = "android.intent.action.CONFIGURATION_CHANGED"; field public static final String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT"; + field public static final String ACTION_CREATE_REMINDER = "android.intent.action.CREATE_REMINDER"; field public static final String ACTION_CREATE_SHORTCUT = "android.intent.action.CREATE_SHORTCUT"; field public static final String ACTION_DATE_CHANGED = "android.intent.action.DATE_CHANGED"; field public static final String ACTION_DEFAULT = "android.intent.action.VIEW"; @@ -10626,6 +10631,7 @@ package android.content { field public static final String EXTRA_SUSPENDED_PACKAGE_EXTRAS = "android.intent.extra.SUSPENDED_PACKAGE_EXTRAS"; field public static final String EXTRA_TEMPLATE = "android.intent.extra.TEMPLATE"; field public static final String EXTRA_TEXT = "android.intent.extra.TEXT"; + field public static final String EXTRA_TIME = "android.intent.extra.TIME"; field public static final String EXTRA_TITLE = "android.intent.extra.TITLE"; field public static final String EXTRA_UID = "android.intent.extra.UID"; field public static final String EXTRA_USER = "android.intent.extra.USER"; @@ -11418,6 +11424,7 @@ package android.content.pm { public final class InstallSourceInfo implements android.os.Parcelable { method public int describeContents(); method @Nullable public String getInitiatingPackageName(); + method @Nullable public android.content.pm.SigningInfo getInitiatingPackageSigningInfo(); method @Nullable public String getInstallingPackageName(); method @Nullable public String getOriginatingPackageName(); method public void writeToParcel(@NonNull android.os.Parcel, int); @@ -11784,6 +11791,7 @@ package android.content.pm { method @NonNull public abstract CharSequence getApplicationLabel(@NonNull android.content.pm.ApplicationInfo); method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull android.content.pm.ApplicationInfo); method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; + method @NonNull public CharSequence getBackgroundPermissionButtonLabel(); method @Nullable public abstract android.content.pm.ChangedPackages getChangedPackages(@IntRange(from=0) int); method public abstract int getComponentEnabledSetting(@NonNull android.content.ComponentName); method @NonNull public abstract android.graphics.drawable.Drawable getDefaultActivityIcon(); @@ -16852,6 +16860,8 @@ package android.hardware.biometrics { method @Nullable public CharSequence getSubtitle(); method @NonNull public CharSequence getTitle(); method public boolean isConfirmationRequired(); + field public static final int AUTHENTICATION_RESULT_TYPE_BIOMETRIC = 2; // 0x2 + field public static final int AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL = 1; // 0x1 field public static final int BIOMETRIC_ACQUIRED_GOOD = 0; // 0x0 field public static final int BIOMETRIC_ACQUIRED_IMAGER_DIRTY = 3; // 0x3 field public static final int BIOMETRIC_ACQUIRED_INSUFFICIENT = 2; // 0x2 @@ -16881,6 +16891,7 @@ package android.hardware.biometrics { } public static class BiometricPrompt.AuthenticationResult { + method public int getAuthenticationType(); method public android.hardware.biometrics.BiometricPrompt.CryptoObject getCryptoObject(); } @@ -17278,6 +17289,7 @@ package android.hardware.camera2 { field public static final int LENS_OPTICAL_STABILIZATION_MODE_ON = 1; // 0x1 field public static final int LENS_POSE_REFERENCE_GYROSCOPE = 1; // 0x1 field public static final int LENS_POSE_REFERENCE_PRIMARY_CAMERA = 0; // 0x0 + field public static final int LENS_POSE_REFERENCE_UNDEFINED = 2; // 0x2 field public static final int LENS_STATE_MOVING = 1; // 0x1 field public static final int LENS_STATE_STATIONARY = 0; // 0x0 field public static final int LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_APPROXIMATE = 0; // 0x0 @@ -27138,9 +27150,11 @@ package android.media { public abstract class VolumeProvider { ctor public VolumeProvider(int, int, int); + ctor public VolumeProvider(int, int, int, @Nullable String); method public final int getCurrentVolume(); method public final int getMaxVolume(); method public final int getVolumeControl(); + method @Nullable public final String getVolumeControlId(); method public void onAdjustVolume(int); method public void onSetVolumeTo(int); method public final void setCurrentVolume(int); @@ -28000,6 +28014,7 @@ package android.media.session { method public int getMaxVolume(); method public int getPlaybackType(); method public int getVolumeControl(); + method @Nullable public String getVolumeControlId(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.media.session.MediaController.PlaybackInfo> CREATOR; field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1 @@ -28701,7 +28716,9 @@ package android.media.tv { ctor public TvInputService(); method public final android.os.IBinder onBind(android.content.Intent); method @Nullable public android.media.tv.TvInputService.RecordingSession onCreateRecordingSession(String); + method @Nullable public android.media.tv.TvInputService.RecordingSession onCreateRecordingSession(@NonNull String, @NonNull String); method @Nullable public abstract android.media.tv.TvInputService.Session onCreateSession(String); + method @Nullable public android.media.tv.TvInputService.Session onCreateSession(@NonNull String, @NonNull String); field public static final String SERVICE_INTERFACE = "android.media.tv.TvInputService"; field public static final String SERVICE_META_DATA = "android.media.tv.input"; } @@ -29639,7 +29656,7 @@ package android.net { method @NonNull public android.net.NetworkRequest.Builder clearCapabilities(); method public android.net.NetworkRequest.Builder removeCapability(int); method public android.net.NetworkRequest.Builder removeTransportType(int); - method public android.net.NetworkRequest.Builder setNetworkSpecifier(String); + method @Deprecated public android.net.NetworkRequest.Builder setNetworkSpecifier(String); method public android.net.NetworkRequest.Builder setNetworkSpecifier(android.net.NetworkSpecifier); } @@ -29738,6 +29755,19 @@ package android.net { method public void onStopped(); } + public final class TelephonyNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable { + method public int describeContents(); + method public int getSubscriptionId(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.TelephonyNetworkSpecifier> CREATOR; + } + + public static final class TelephonyNetworkSpecifier.Builder { + ctor public TelephonyNetworkSpecifier.Builder(); + method @NonNull public android.net.TelephonyNetworkSpecifier build(); + method @NonNull public android.net.TelephonyNetworkSpecifier.Builder setSubscriptionId(int); + } + public class TrafficStats { ctor public TrafficStats(); method public static void clearThreadStatsTag(); @@ -30495,6 +30525,11 @@ package android.net.wifi { field @Deprecated public static final String[] strings; } + @Deprecated public static class WifiConfiguration.SuiteBCipher { + field @Deprecated public static final int ECDHE_ECDSA = 0; // 0x0 + field @Deprecated public static final int ECDHE_RSA = 1; // 0x1 + } + public class WifiEnterpriseConfig implements android.os.Parcelable { ctor public WifiEnterpriseConfig(); ctor public WifiEnterpriseConfig(android.net.wifi.WifiEnterpriseConfig); @@ -30505,6 +30540,7 @@ package android.net.wifi { method @Nullable public java.security.cert.X509Certificate[] getCaCertificates(); method public java.security.cert.X509Certificate getClientCertificate(); method @Nullable public java.security.cert.X509Certificate[] getClientCertificateChain(); + method @Nullable public java.security.PrivateKey getClientPrivateKey(); method public String getDomainSuffixMatch(); method public int getEapMethod(); method public String getIdentity(); @@ -30619,6 +30655,7 @@ package android.net.wifi { method public boolean isP2pSupported(); method public boolean isPreferredNetworkOffloadSupported(); method @Deprecated public boolean isScanAlwaysAvailable(); + method public boolean isStaApConcurrencySupported(); method public boolean isTdlsSupported(); method public boolean isWapiSupported(); method public boolean isWifiEnabled(); @@ -30774,6 +30811,7 @@ package android.net.wifi { method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsAppInteractionRequired(boolean); method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsEnhancedOpen(boolean); method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsHiddenSsid(boolean); + method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsInitialAutoJoinEnabled(boolean); method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsMetered(boolean); method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsUserInteractionRequired(boolean); method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPasspointConfig(@NonNull android.net.wifi.hotspot2.PasspointConfiguration); @@ -39031,7 +39069,7 @@ package android.provider { field public static final String COLUMN_MIME_TYPE = "mime_type"; field public static final String COLUMN_SIZE = "_size"; field public static final String COLUMN_SUMMARY = "summary"; - field public static final int FLAG_DIR_BLOCKS_TREE = 32768; // 0x8000 + field public static final int FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE = 32768; // 0x8000 field public static final int FLAG_DIR_PREFERS_GRID = 16; // 0x10 field public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 32; // 0x20 field public static final int FLAG_DIR_SUPPORTS_CREATE = 8; // 0x8 @@ -39203,6 +39241,7 @@ package android.provider { method @NonNull public static android.app.PendingIntent createWriteRequest(@NonNull android.content.ContentResolver, @NonNull java.util.Collection<android.net.Uri>); method @Nullable public static android.net.Uri getDocumentUri(@NonNull android.content.Context, @NonNull android.net.Uri); method @NonNull public static java.util.Set<java.lang.String> getExternalVolumeNames(@NonNull android.content.Context); + method public static long getGeneration(@NonNull android.content.Context, @NonNull String); method public static android.net.Uri getMediaScannerUri(); method @Nullable public static android.net.Uri getMediaUri(@NonNull android.content.Context, @NonNull android.net.Uri); method @NonNull public static java.util.Set<java.lang.String> getRecentExternalVolumeNames(@NonNull android.content.Context); @@ -39513,6 +39552,8 @@ package android.provider { field public static final String DISPLAY_NAME = "_display_name"; field public static final String DOCUMENT_ID = "document_id"; field public static final String DURATION = "duration"; + field public static final String GENERATION_ADDED = "generation_added"; + field public static final String GENERATION_MODIFIED = "generation_modified"; field public static final String GENRE = "genre"; field public static final String HEIGHT = "height"; field public static final String INSTANCE_ID = "instance_id"; @@ -44045,6 +44086,7 @@ package android.telecom { method public java.util.List<android.telecom.Call> getChildren(); method public java.util.List<android.telecom.Call> getConferenceableCalls(); method public android.telecom.Call.Details getDetails(); + method @Nullable public android.telecom.Call getGenericConferenceActiveChildCall(); method public android.telecom.Call getParent(); method public String getRemainingPostDialSequence(); method @Nullable public android.telecom.Call.RttCall getRttCall(); @@ -44128,6 +44170,7 @@ package android.telecom { method public int getCallerDisplayNamePresentation(); method public int getCallerNumberVerificationStatus(); method public final long getConnectTimeMillis(); + method @Nullable public String getContactDisplayName(); method public long getCreationTimeMillis(); method public android.telecom.DisconnectCause getDisconnectCause(); method public android.os.Bundle getExtras(); @@ -45137,6 +45180,39 @@ package android.telephony { field public static final int PRIORITY_MED = 2; // 0x2 } + public final class BarringInfo implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public android.telephony.BarringInfo.BarringServiceInfo getBarringServiceInfo(int); + method public boolean isServiceBarred(int); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field public static final int BARRING_SERVICE_TYPE_CS_FALLBACK = 5; // 0x5 + field public static final int BARRING_SERVICE_TYPE_CS_SERVICE = 0; // 0x0 + field public static final int BARRING_SERVICE_TYPE_CS_VOICE = 2; // 0x2 + field public static final int BARRING_SERVICE_TYPE_EMERGENCY = 8; // 0x8 + field public static final int BARRING_SERVICE_TYPE_MMTEL_VIDEO = 7; // 0x7 + field public static final int BARRING_SERVICE_TYPE_MMTEL_VOICE = 6; // 0x6 + field public static final int BARRING_SERVICE_TYPE_MO_DATA = 4; // 0x4 + field public static final int BARRING_SERVICE_TYPE_MO_SIGNALLING = 3; // 0x3 + field public static final int BARRING_SERVICE_TYPE_PS_SERVICE = 1; // 0x1 + field public static final int BARRING_SERVICE_TYPE_SMS = 9; // 0x9 + field @NonNull public static final android.os.Parcelable.Creator<android.telephony.BarringInfo> CREATOR; + } + + public static final class BarringInfo.BarringServiceInfo implements android.os.Parcelable { + method public int describeContents(); + method public int getBarringType(); + method public int getConditionalBarringFactor(); + method public int getConditionalBarringTimeSeconds(); + method public boolean isBarred(); + method public boolean isConditionallyBarred(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field public static final int BARRING_TYPE_CONDITIONAL = 1; // 0x1 + field public static final int BARRING_TYPE_NONE = 0; // 0x0 + field public static final int BARRING_TYPE_UNCONDITIONAL = 2; // 0x2 + field public static final int BARRING_TYPE_UNKNOWN = -1; // 0xffffffff + field @NonNull public static final android.os.Parcelable.Creator<android.telephony.BarringInfo.BarringServiceInfo> CREATOR; + } + public class CarrierConfigManager { method @Nullable public android.os.PersistableBundle getConfig(); method @Nullable public android.os.PersistableBundle getConfigByComponentForSubId(@NonNull String, int); @@ -45149,6 +45225,9 @@ package android.telephony { field public static final String ENABLE_EAP_METHOD_PREFIX_BOOL = "enable_eap_method_prefix_bool"; field public static final String EXTRA_SLOT_INDEX = "android.telephony.extra.SLOT_INDEX"; field public static final String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX"; + field public static final String IMSI_KEY_AVAILABILITY_INT = "imsi_key_availability_int"; + field public static final String KEY_5G_ICON_CONFIGURATION_STRING = "5g_icon_configuration_string"; + field public static final String KEY_5G_ICON_DISPLAY_GRACE_PERIOD_SEC_INT = "5g_icon_display_grace_period_sec_int"; field public static final String KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY = "5g_nr_ssrsrp_thresholds_int_array"; field public static final String KEY_5G_NR_SSRSRQ_THRESHOLDS_INT_ARRAY = "5g_nr_ssrsrq_thresholds_int_array"; field public static final String KEY_5G_NR_SSSINR_THRESHOLDS_INT_ARRAY = "5g_nr_sssinr_thresholds_int_array"; @@ -45162,18 +45241,32 @@ package android.telephony { field public static final String KEY_ALLOW_LOCAL_DTMF_TONES_BOOL = "allow_local_dtmf_tones_bool"; field public static final String KEY_ALLOW_MERGE_WIFI_CALLS_WHEN_VOWIFI_OFF_BOOL = "allow_merge_wifi_calls_when_vowifi_off_bool"; field public static final String KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL = "allow_non_emergency_calls_in_ecm_bool"; + field public static final String KEY_ALLOW_VIDEO_CALLING_FALLBACK_BOOL = "allow_video_calling_fallback_bool"; + field public static final String KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL = "always_show_data_rat_icon_bool"; field @Deprecated public static final String KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL = "always_show_emergency_alert_onoff_bool"; + field public static final String KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN = "always_show_primary_signal_bar_in_opportunistic_network_boolean"; field public static final String KEY_APN_EXPAND_BOOL = "apn_expand_bool"; + field public static final String KEY_APN_SETTINGS_DEFAULT_APN_TYPES_STRING_ARRAY = "apn_settings_default_apn_types_string_array"; field public static final String KEY_AUTO_RETRY_ENABLED_BOOL = "auto_retry_enabled_bool"; field public static final String KEY_CALL_BARRING_SUPPORTS_DEACTIVATE_ALL_BOOL = "call_barring_supports_deactivate_all_bool"; field public static final String KEY_CALL_BARRING_SUPPORTS_PASSWORD_CHANGE_BOOL = "call_barring_supports_password_change_bool"; field public static final String KEY_CALL_BARRING_VISIBILITY_BOOL = "call_barring_visibility_bool"; field public static final String KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY = "call_forwarding_blocks_while_roaming_string_array"; + field public static final String KEY_CALL_REDIRECTION_SERVICE_COMPONENT_NAME_STRING = "call_redirection_service_component_name_string"; + field public static final String KEY_CARRIER_ALLOW_DEFLECT_IMS_CALL_BOOL = "carrier_allow_deflect_ims_call_bool"; field public static final String KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL = "carrier_allow_turnoff_ims_bool"; field public static final String KEY_CARRIER_APP_REQUIRED_DURING_SIM_SETUP_BOOL = "carrier_app_required_during_setup_bool"; field public static final String KEY_CARRIER_CALL_SCREENING_APP_STRING = "call_screening_app"; + field public static final String KEY_CARRIER_CERTIFICATE_STRING_ARRAY = "carrier_certificate_string_array"; + field public static final String KEY_CARRIER_CONFIG_APPLIED_BOOL = "carrier_config_applied_bool"; field public static final String KEY_CARRIER_CONFIG_VERSION_STRING = "carrier_config_version_string"; field public static final String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS = "carrier_data_call_permanent_failure_strings"; + field public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_DCFAILURE_STRING_ARRAY = "carrier_default_actions_on_dcfailure_string_array"; + field public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_DEFAULT_NETWORK_AVAILABLE = "carrier_default_actions_on_default_network_available_string_array"; + field public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY = "carrier_default_actions_on_redirection_string_array"; + field public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_RESET = "carrier_default_actions_on_reset_string_array"; + field public static final String KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY = "carrier_default_redirection_url_string_array"; + field public static final String KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL = "carrier_default_wfc_ims_enabled_bool"; field public static final String KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT = "carrier_default_wfc_ims_mode_int"; field public static final String KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT = "carrier_default_wfc_ims_roaming_mode_int"; field @Deprecated public static final String KEY_CARRIER_FORCE_DISABLE_ETWS_CMAS_TEST_BOOL = "carrier_force_disable_etws_cmas_test_bool"; @@ -45185,11 +45278,14 @@ package android.telephony { field public static final String KEY_CARRIER_INSTANT_LETTERING_LENGTH_LIMIT_INT = "carrier_instant_lettering_length_limit_int"; field public static final String KEY_CARRIER_NAME_OVERRIDE_BOOL = "carrier_name_override_bool"; field public static final String KEY_CARRIER_NAME_STRING = "carrier_name_string"; + field public static final String KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL = "carrier_rcs_provisioning_required_bool"; + field public static final String KEY_CARRIER_SETTINGS_ACTIVITY_COMPONENT_NAME_STRING = "carrier_settings_activity_component_name_string"; field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool"; field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool"; field public static final String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL = "carrier_use_ims_first_for_emergency_bool"; field public static final String KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL = "carrier_ut_provisioning_required_bool"; field public static final String KEY_CARRIER_VOLTE_AVAILABLE_BOOL = "carrier_volte_available_bool"; + field public static final String KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL = "carrier_volte_override_wfc_provisioning_bool"; field public static final String KEY_CARRIER_VOLTE_PROVISIONED_BOOL = "carrier_volte_provisioned_bool"; field public static final String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool"; field public static final String KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL = "carrier_volte_tty_supported_bool"; @@ -45203,6 +45299,7 @@ package android.telephony { field public static final String KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY = "cdma_nonroaming_networks_string_array"; field public static final String KEY_CDMA_ROAMING_MODE_INT = "cdma_roaming_mode_int"; field public static final String KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY = "cdma_roaming_networks_string_array"; + field public static final String KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL = "check_pricing_with_carrier_data_roaming_bool"; field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_BOOL = "ci_action_on_sys_update_bool"; field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING = "ci_action_on_sys_update_extra_string"; field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING = "ci_action_on_sys_update_extra_val_string"; @@ -45212,6 +45309,7 @@ package android.telephony { field public static final String KEY_CONFIG_IMS_RCS_PACKAGE_OVERRIDE_STRING = "config_ims_rcs_package_override_string"; field public static final String KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING = "config_plans_package_override_string"; field public static final String KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL = "config_telephony_use_own_number_for_voicemail_bool"; + field public static final String KEY_CONFIG_WIFI_DISABLE_IN_ECBM = "config_wifi_disable_in_ecbm"; field public static final String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool"; field public static final String KEY_DATA_LIMIT_NOTIFICATION_BOOL = "data_limit_notification_bool"; field public static final String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long"; @@ -45223,6 +45321,7 @@ package android.telephony { field public static final String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string"; field public static final String KEY_DIAL_STRING_REPLACE_STRING_ARRAY = "dial_string_replace_string_array"; field public static final String KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL = "disable_cdma_activation_code_bool"; + field public static final String KEY_DISABLE_CHARGE_INDICATION_BOOL = "disable_charge_indication_bool"; field public static final String KEY_DISABLE_SUPPLEMENTARY_SERVICES_IN_AIRPLANE_MODE_BOOL = "disable_supplementary_services_in_airplane_mode_bool"; field public static final String KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY = "disconnect_cause_play_busytone_int_array"; field public static final String KEY_DISPLAY_HD_AUDIO_PROPERTY_BOOL = "display_hd_audio_property_bool"; @@ -45232,9 +45331,13 @@ package android.telephony { field public static final String KEY_EDITABLE_ENHANCED_4G_LTE_BOOL = "editable_enhanced_4g_lte_bool"; field public static final String KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL = "editable_voicemail_number_bool"; field public static final String KEY_EDITABLE_VOICEMAIL_NUMBER_SETTING_BOOL = "editable_voicemail_number_setting_bool"; + field public static final String KEY_EDITABLE_WFC_MODE_BOOL = "editable_wfc_mode_bool"; + field public static final String KEY_EDITABLE_WFC_ROAMING_MODE_BOOL = "editable_wfc_roaming_mode_bool"; + field public static final String KEY_EMERGENCY_NOTIFICATION_DELAY_INT = "emergency_notification_delay_int"; field public static final String KEY_EMERGENCY_NUMBER_PREFIX_STRING_ARRAY = "emergency_number_prefix_string_array"; field public static final String KEY_ENABLE_DIALER_KEY_VIBRATION_BOOL = "enable_dialer_key_vibration_bool"; field public static final String KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL = "enhanced_4g_lte_on_by_default_bool"; + field public static final String KEY_ENHANCED_4G_LTE_TITLE_VARIANT_INT = "enhanced_4g_lte_title_variant_int"; field public static final String KEY_FORCE_HOME_NETWORK_BOOL = "force_home_network_bool"; field public static final String KEY_GSM_DTMF_TONE_DELAY_INT = "gsm_dtmf_tone_delay_int"; field public static final String KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY = "gsm_nonroaming_networks_string_array"; @@ -45243,13 +45346,17 @@ package android.telephony { field public static final String KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL = "hide_carrier_network_settings_bool"; field public static final String KEY_HIDE_ENHANCED_4G_LTE_BOOL = "hide_enhanced_4g_lte_bool"; field public static final String KEY_HIDE_IMS_APN_BOOL = "hide_ims_apn_bool"; + field public static final String KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL = "hide_lte_plus_data_icon_bool"; field public static final String KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL = "hide_preferred_network_type_bool"; field public static final String KEY_HIDE_PRESET_APN_DETAILS_BOOL = "hide_preset_apn_details_bool"; field public static final String KEY_HIDE_SIM_LOCK_SETTINGS_BOOL = "hide_sim_lock_settings_bool"; + field public static final String KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS = "ignore_data_enabled_changed_for_video_calls"; + field public static final String KEY_IGNORE_RTT_MODE_SETTING_BOOL = "ignore_rtt_mode_setting_bool"; field public static final String KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL = "ignore_sim_network_locked_events_bool"; field public static final String KEY_IMS_CONFERENCE_SIZE_LIMIT_INT = "ims_conference_size_limit_int"; field public static final String KEY_IMS_DTMF_TONE_DELAY_INT = "ims_dtmf_tone_delay_int"; field public static final String KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL = "is_ims_conference_size_enforced_bool"; + field public static final String KEY_LTE_ENABLED_BOOL = "lte_enabled_bool"; field public static final String KEY_LTE_RSRQ_THRESHOLDS_INT_ARRAY = "lte_rsrq_thresholds_int_array"; field public static final String KEY_LTE_RSSNR_THRESHOLDS_INT_ARRAY = "lte_rssnr_thresholds_int_array"; field public static final String KEY_MDN_IS_ADDITIONAL_VOICEMAIL_NUMBER_BOOL = "mdn_is_additional_voicemail_number_bool"; @@ -45285,6 +45392,7 @@ package android.telephony { field public static final String KEY_MMS_UA_PROF_URL_STRING = "uaProfUrl"; field public static final String KEY_MMS_USER_AGENT_STRING = "userAgent"; field public static final String KEY_MONTHLY_DATA_CYCLE_DAY_INT = "monthly_data_cycle_day_int"; + field public static final String KEY_ONLY_AUTO_SELECT_IN_HOME_NETWORK_BOOL = "only_auto_select_in_home_network"; field public static final String KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY = "only_single_dc_allowed_int_array"; field public static final String KEY_OPERATOR_SELECTION_EXPAND_BOOL = "operator_selection_expand_bool"; field public static final String KEY_OPPORTUNISTIC_NETWORK_DATA_SWITCH_HYSTERESIS_TIME_LONG = "opportunistic_network_data_switch_hysteresis_time_long"; @@ -45298,15 +45406,24 @@ package android.telephony { field public static final String KEY_PREVENT_CLIR_ACTIVATION_AND_DEACTIVATION_CODE_BOOL = "prevent_clir_activation_and_deactivation_code_bool"; field public static final String KEY_RADIO_RESTART_FAILURE_CAUSES_INT_ARRAY = "radio_restart_failure_causes_int_array"; field public static final String KEY_RCS_CONFIG_SERVER_URL_STRING = "rcs_config_server_url_string"; + field public static final String KEY_READ_ONLY_APN_FIELDS_STRING_ARRAY = "read_only_apn_fields_string_array"; + field public static final String KEY_READ_ONLY_APN_TYPES_STRING_ARRAY = "read_only_apn_types_string_array"; field public static final String KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL = "require_entitlement_checks_bool"; field @Deprecated public static final String KEY_RESTART_RADIO_ON_PDP_FAIL_REGULAR_DEACTIVATION_BOOL = "restart_radio_on_pdp_fail_regular_deactivation_bool"; field public static final String KEY_RTT_SUPPORTED_BOOL = "rtt_supported_bool"; + field public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL = "show_4g_for_3g_data_icon_bool"; + field public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL = "show_4g_for_lte_data_icon_bool"; field public static final String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool"; + field public static final String KEY_SHOW_BLOCKING_PAY_PHONE_OPTION_BOOL = "show_blocking_pay_phone_option_bool"; field public static final String KEY_SHOW_CALL_BLOCKING_DISABLED_NOTIFICATION_ALWAYS_BOOL = "show_call_blocking_disabled_notification_always_bool"; + field public static final String KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING = "show_carrier_data_icon_pattern_string"; field public static final String KEY_SHOW_CDMA_CHOICES_BOOL = "show_cdma_choices_bool"; field public static final String KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL = "show_iccid_in_sim_status_bool"; + field public static final String KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL = "show_ims_registration_status_bool"; field public static final String KEY_SHOW_ONSCREEN_DIAL_BUTTON_BOOL = "show_onscreen_dial_button_bool"; field public static final String KEY_SHOW_SIGNAL_STRENGTH_IN_SIM_STATUS_BOOL = "show_signal_strength_in_sim_status_bool"; + field public static final String KEY_SHOW_VIDEO_CALL_CHARGES_ALERT_DIALOG_BOOL = "show_video_call_charges_alert_dialog_bool"; + field public static final String KEY_SHOW_WFC_LOCATION_PRIVACY_POLICY_BOOL = "show_wfc_location_privacy_policy_bool"; field public static final String KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL = "simplified_network_settings_bool"; field public static final String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool"; field public static final String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool"; @@ -45314,14 +45431,20 @@ package android.telephony { field public static final String KEY_SUPPORT_CLIR_NETWORK_DEFAULT_BOOL = "support_clir_network_default_bool"; field public static final String KEY_SUPPORT_CONFERENCE_CALL_BOOL = "support_conference_call_bool"; field public static final String KEY_SUPPORT_EMERGENCY_SMS_OVER_IMS_BOOL = "support_emergency_sms_over_ims_bool"; + field public static final String KEY_SUPPORT_ENHANCED_CALL_BLOCKING_BOOL = "support_enhanced_call_blocking_bool"; + field public static final String KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_BOOL = "support_ims_conference_event_package_bool"; field public static final String KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL = "support_pause_ims_video_calls_bool"; field public static final String KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL = "support_swap_after_merge_bool"; + field public static final String KEY_SUPPORT_TDSCDMA_BOOL = "support_tdscdma_bool"; + field public static final String KEY_SUPPORT_TDSCDMA_ROAMING_NETWORKS_STRING_ARRAY = "support_tdscdma_roaming_networks_string_array"; field public static final String KEY_TREAT_DOWNGRADED_VIDEO_CALLS_AS_VIDEO_CALLS_BOOL = "treat_downgraded_video_calls_as_video_calls_bool"; field public static final String KEY_TTY_SUPPORTED_BOOL = "tty_supported_bool"; + field public static final String KEY_UNLOGGABLE_NUMBERS_STRING_ARRAY = "unloggable_numbers_string_array"; field public static final String KEY_USE_HFA_FOR_PROVISIONING_BOOL = "use_hfa_for_provisioning_bool"; field public static final String KEY_USE_OTASP_FOR_PROVISIONING_BOOL = "use_otasp_for_provisioning_bool"; field public static final String KEY_USE_RCS_PRESENCE_BOOL = "use_rcs_presence_bool"; field public static final String KEY_USE_RCS_SIP_OPTIONS_BOOL = "use_rcs_sip_options_bool"; + field public static final String KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL = "use_wfc_home_network_mode_in_roaming_network_bool"; field public static final String KEY_VOICEMAIL_NOTIFICATION_PERSISTENT_BOOL = "voicemail_notification_persistent_bool"; field public static final String KEY_VOICE_PRIVACY_DISABLE_UI_BOOL = "voice_privacy_disable_ui_bool"; field public static final String KEY_VOLTE_REPLACEMENT_RAT_INT = "volte_replacement_rat_int"; @@ -45334,6 +45457,8 @@ package android.telephony { field public static final String KEY_VVM_PREFETCH_BOOL = "vvm_prefetch_bool"; field public static final String KEY_VVM_SSL_ENABLED_BOOL = "vvm_ssl_enabled_bool"; field public static final String KEY_VVM_TYPE_STRING = "vvm_type_string"; + field public static final String KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING = "wfc_emergency_address_carrier_app_string"; + field public static final String KEY_WORLD_MODE_ENABLED_BOOL = "world_mode_enabled_bool"; field public static final String KEY_WORLD_PHONE_BOOL = "world_phone_bool"; } @@ -45781,6 +45906,7 @@ package android.telephony { ctor public PhoneStateListener(); ctor public PhoneStateListener(@NonNull java.util.concurrent.Executor); method public void onActiveDataSubscriptionIdChanged(int); + method public void onBarringInfoChanged(@NonNull android.telephony.BarringInfo); method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onCallDisconnectCauseChanged(int, int); method public void onCallForwardingIndicatorChanged(boolean); method public void onCallStateChanged(int, String); @@ -45798,6 +45924,7 @@ package android.telephony { method public void onSignalStrengthsChanged(android.telephony.SignalStrength); method public void onUserMobileDataStateChanged(boolean); field public static final int LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE = 4194304; // 0x400000 + field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int LISTEN_BARRING_INFO = -2147483648; // 0x80000000 field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_CALL_DISCONNECT_CAUSES = 33554432; // 0x2000000 field public static final int LISTEN_CALL_FORWARDING_INDICATOR = 8; // 0x8 field public static final int LISTEN_CALL_STATE = 32; // 0x20 @@ -46396,6 +46523,7 @@ package android.telephony { field public static final int NETWORK_TYPE_UNKNOWN = 0; // 0x0 field public static final int PHONE_TYPE_CDMA = 2; // 0x2 field public static final int PHONE_TYPE_GSM = 1; // 0x1 + field public static final int PHONE_TYPE_IMS = 5; // 0x5 field public static final int PHONE_TYPE_NONE = 0; // 0x0 field public static final int PHONE_TYPE_SIP = 3; // 0x3 field public static final int SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION = 2; // 0x2 @@ -54819,6 +54947,7 @@ package android.view.inputmethod { public final class InlineSuggestionsRequest implements android.os.Parcelable { method public int describeContents(); + method @NonNull public String getHostPackageName(); method public int getMaxSuggestionCount(); method @NonNull public java.util.List<android.view.inline.InlinePresentationSpec> getPresentationSpecs(); method public void writeToParcel(@NonNull android.os.Parcel, int); @@ -58842,7 +58971,7 @@ package android.widget { method @android.view.ViewDebug.ExportedProperty public CharSequence getFormat24Hour(); method public String getTimeZone(); method public boolean is24HourModeEnabled(); - method public void refresh(); + method public void refreshTime(); method public void setFormat12Hour(CharSequence); method public void setFormat24Hour(CharSequence); method public void setTimeZone(String); @@ -59157,6 +59286,7 @@ package android.widget { public class Toast { ctor public Toast(android.content.Context); + method public void addCallback(@NonNull android.widget.Toast.Callback); method public void cancel(); method public int getDuration(); method public int getGravity(); @@ -59167,6 +59297,7 @@ package android.widget { method public int getYOffset(); method public static android.widget.Toast makeText(android.content.Context, CharSequence, int); method public static android.widget.Toast makeText(android.content.Context, @StringRes int, int) throws android.content.res.Resources.NotFoundException; + method public void removeCallback(@NonNull android.widget.Toast.Callback); method public void setDuration(int); method public void setGravity(int, int, int); method public void setMargin(float, float); @@ -59178,6 +59309,12 @@ package android.widget { field public static final int LENGTH_SHORT = 0; // 0x0 } + public abstract static class Toast.Callback { + ctor public Toast.Callback(); + method public void onToastHidden(); + method public void onToastShown(); + } + public class ToggleButton extends android.widget.CompoundButton { ctor public ToggleButton(android.content.Context, android.util.AttributeSet, int, int); ctor public ToggleButton(android.content.Context, android.util.AttributeSet, int); diff --git a/api/module-app-current.txt b/api/module-app-current.txt index d802177e249b..4307e675e431 100644 --- a/api/module-app-current.txt +++ b/api/module-app-current.txt @@ -1 +1,9 @@ // Signature format: 2.0 +package android.app { + + public final class NotificationChannel implements android.os.Parcelable { + method public void setBlockableSystem(boolean); + } + +} + diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt index d802177e249b..1cb1c20c738c 100644 --- a/api/module-lib-current.txt +++ b/api/module-lib-current.txt @@ -1 +1,134 @@ // Signature format: 2.0 +package android.app.timedetector { + + public final class PhoneTimeSuggestion implements android.os.Parcelable { + method public void addDebugInfo(@NonNull String); + method public void addDebugInfo(@NonNull java.util.List<java.lang.String>); + method public int describeContents(); + method @NonNull public java.util.List<java.lang.String> getDebugInfo(); + method public int getPhoneId(); + method @Nullable public android.os.TimestampedValue<java.lang.Long> getUtcTime(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.timedetector.PhoneTimeSuggestion> CREATOR; + } + + public static final class PhoneTimeSuggestion.Builder { + ctor public PhoneTimeSuggestion.Builder(int); + method @NonNull public android.app.timedetector.PhoneTimeSuggestion.Builder addDebugInfo(@NonNull String); + method @NonNull public android.app.timedetector.PhoneTimeSuggestion build(); + method @NonNull public android.app.timedetector.PhoneTimeSuggestion.Builder setUtcTime(@Nullable android.os.TimestampedValue<java.lang.Long>); + } + + public class TimeDetector { + method @RequiresPermission("android.permission.SUGGEST_PHONE_TIME_AND_ZONE") public void suggestPhoneTime(@NonNull android.app.timedetector.PhoneTimeSuggestion); + } + +} + +package android.app.timezonedetector { + + public final class PhoneTimeZoneSuggestion implements android.os.Parcelable { + method public void addDebugInfo(@NonNull String); + method public void addDebugInfo(@NonNull java.util.List<java.lang.String>); + method @NonNull public static android.app.timezonedetector.PhoneTimeZoneSuggestion createEmptySuggestion(int, @NonNull String); + method public int describeContents(); + method @NonNull public java.util.List<java.lang.String> getDebugInfo(); + method public int getMatchType(); + method public int getPhoneId(); + method public int getQuality(); + method @Nullable public String getZoneId(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.timezonedetector.PhoneTimeZoneSuggestion> CREATOR; + field public static final int MATCH_TYPE_EMULATOR_ZONE_ID = 4; // 0x4 + field public static final int MATCH_TYPE_NA = 0; // 0x0 + field public static final int MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET = 3; // 0x3 + field public static final int MATCH_TYPE_NETWORK_COUNTRY_ONLY = 2; // 0x2 + field public static final int MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY = 5; // 0x5 + field public static final int QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS = 3; // 0x3 + field public static final int QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET = 2; // 0x2 + field public static final int QUALITY_NA = 0; // 0x0 + field public static final int QUALITY_SINGLE_ZONE = 1; // 0x1 + } + + public static final class PhoneTimeZoneSuggestion.Builder { + ctor public PhoneTimeZoneSuggestion.Builder(int); + method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion.Builder addDebugInfo(@NonNull String); + method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion build(); + method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion.Builder setMatchType(int); + method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion.Builder setQuality(int); + method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion.Builder setZoneId(@Nullable String); + } + + public class TimeZoneDetector { + method @RequiresPermission("android.permission.SUGGEST_PHONE_TIME_AND_ZONE") public void suggestPhoneTimeZone(@NonNull android.app.timezonedetector.PhoneTimeZoneSuggestion); + } + +} + +package android.os { + + public final class TimestampedValue<T> implements android.os.Parcelable { + ctor public TimestampedValue(long, @Nullable T); + method public int describeContents(); + method public long getReferenceTimeMillis(); + method @Nullable public T getValue(); + method public static long referenceTimeDifference(@NonNull android.os.TimestampedValue<?>, @NonNull android.os.TimestampedValue<?>); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.os.TimestampedValue<?>> CREATOR; + } + +} + +package android.timezone { + + public final class CountryTimeZones { + method @Nullable public android.icu.util.TimeZone getDefaultTimeZone(); + method @Nullable public String getDefaultTimeZoneId(); + method @NonNull public java.util.List<android.timezone.CountryTimeZones.TimeZoneMapping> getEffectiveTimeZoneMappingsAt(long); + method public boolean hasUtcZone(long); + method public boolean isDefaultTimeZoneBoosted(); + method public boolean isForCountryCode(@NonNull String); + method @Nullable public android.timezone.CountryTimeZones.OffsetResult lookupByOffsetWithBias(int, @Nullable Boolean, @Nullable Integer, long, @Nullable android.icu.util.TimeZone); + } + + public static final class CountryTimeZones.OffsetResult { + ctor public CountryTimeZones.OffsetResult(@NonNull android.icu.util.TimeZone, boolean); + method @NonNull public android.icu.util.TimeZone getTimeZone(); + method public boolean isOnlyMatch(); + } + + public static final class CountryTimeZones.TimeZoneMapping { + method @Nullable public android.icu.util.TimeZone getTimeZone(); + method @NonNull public String getTimeZoneId(); + } + + public class TelephonyLookup { + method @NonNull public static android.timezone.TelephonyLookup getInstance(); + method @Nullable public android.timezone.TelephonyNetworkFinder getTelephonyNetworkFinder(); + } + + public class TelephonyNetwork { + method @NonNull public String getCountryIsoCode(); + method @NonNull public String getMcc(); + method @NonNull public String getMnc(); + } + + public class TelephonyNetworkFinder { + method @Nullable public android.timezone.TelephonyNetwork findNetworkByMccMnc(@NonNull String, @NonNull String); + } + + public final class TimeZoneFinder { + method @NonNull public static android.timezone.TimeZoneFinder getInstance(); + method @Nullable public android.timezone.CountryTimeZones lookupCountryTimeZones(@NonNull String); + } + +} + +package android.util { + + public final class Log { + method public static int logToRadioBuffer(int, @Nullable String, @Nullable String); + } + +} + diff --git a/api/system-current.txt b/api/system-current.txt index 4bbf7889a118..e532a3afab8a 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -126,6 +126,7 @@ package android { field @Deprecated public static final String MODIFY_NETWORK_ACCOUNTING = "android.permission.MODIFY_NETWORK_ACCOUNTING"; field public static final String MODIFY_PARENTAL_CONTROLS = "android.permission.MODIFY_PARENTAL_CONTROLS"; field public static final String MODIFY_QUIET_MODE = "android.permission.MODIFY_QUIET_MODE"; + field public static final String MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE = "android.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE"; field public static final String MOVE_PACKAGE = "android.permission.MOVE_PACKAGE"; field public static final String NETWORK_AIRPLANE_MODE = "android.permission.NETWORK_AIRPLANE_MODE"; field public static final String NETWORK_CARRIER_PROVISIONING = "android.permission.NETWORK_CARRIER_PROVISIONING"; @@ -208,9 +209,11 @@ package android { field public static final String STOP_APP_SWITCHES = "android.permission.STOP_APP_SWITCHES"; field public static final String SUBSTITUTE_NOTIFICATION_APP_NAME = "android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"; field public static final String SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON = "android.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON"; + field public static final String SUGGEST_PHONE_TIME_AND_ZONE = "android.permission.SUGGEST_PHONE_TIME_AND_ZONE"; field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS"; field public static final String SYSTEM_CAMERA = "android.permission.SYSTEM_CAMERA"; field public static final String TETHER_PRIVILEGED = "android.permission.TETHER_PRIVILEGED"; + field public static final String TUNER_RESOURCE_ACCESS = "android.permission.TUNER_RESOURCE_ACCESS"; field public static final String TV_INPUT_HARDWARE = "android.permission.TV_INPUT_HARDWARE"; field public static final String TV_VIRTUAL_REMOTE_CONTROLLER = "android.permission.TV_VIRTUAL_REMOTE_CONTROLLER"; field public static final String UNLIMITED_SHORTCUTS_API_CALLS = "android.permission.UNLIMITED_SHORTCUTS_API_CALLS"; @@ -240,8 +243,10 @@ package android { public static final class R.attr { field public static final int allowClearUserDataOnFailedRestore = 16844288; // 0x1010600 field public static final int isVrOnly = 16844152; // 0x1010578 + field public static final int minExtensionVersion = 16844306; // 0x1010612 field public static final int requiredSystemPropertyName = 16844133; // 0x1010565 field public static final int requiredSystemPropertyValue = 16844134; // 0x1010566 + field public static final int sdkVersion = 16844305; // 0x1010611 field public static final int supportsAmbientMode = 16844173; // 0x101058d field public static final int userRestriction = 16844164; // 0x1010584 } @@ -326,6 +331,7 @@ package android.app { method public void setDeviceLocales(@NonNull android.os.LocaleList); method @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS) public static void setPersistentVrThread(int); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean switchUser(@NonNull android.os.UserHandle); + method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String); } public static interface ActivityManager.OnUidImportanceListener { @@ -788,6 +794,7 @@ package android.app { package android.app.admin { public class DevicePolicyManager { + method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwner(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.content.ComponentName getDeviceOwnerComponentOnAnyUser(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwnerNameOnAnyUser(); @@ -803,7 +810,6 @@ package android.app.admin { method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioned(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioningConfigApplied(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isManagedKiosk(); - method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isOrganizationOwnedDeviceWithManagedProfile(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isUnattendedManagedKiosk(); method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long); method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long, boolean); @@ -1034,6 +1040,7 @@ package android.app.backup { field public static final int AGENT_ERROR = -1003; // 0xfffffc15 field public static final int AGENT_UNKNOWN = -1004; // 0xfffffc14 field public static final String EXTRA_TRANSPORT_REGISTRATION = "android.app.backup.extra.TRANSPORT_REGISTRATION"; + field public static final int FLAG_DATA_NOT_CHANGED = 8; // 0x8 field public static final int FLAG_INCREMENTAL = 2; // 0x2 field public static final int FLAG_NON_INCREMENTAL = 4; // 0x4 field public static final int FLAG_USER_INITIATED = 1; // 0x1 @@ -1700,7 +1707,7 @@ package android.content { method public abstract void sendBroadcast(android.content.Intent, @Nullable String, @Nullable android.os.Bundle); method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public abstract void sendBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, @Nullable String, @Nullable android.os.Bundle); method public abstract void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable android.os.Bundle, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle); - method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle); + method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle); field public static final String APP_INTEGRITY_SERVICE = "app_integrity"; field public static final String APP_PREDICTION_SERVICE = "app_prediction"; field public static final String BACKUP_SERVICE = "backup"; @@ -1713,6 +1720,7 @@ package android.content { field public static final String NETD_SERVICE = "netd"; field public static final String NETWORK_POLICY_SERVICE = "netpolicy"; field public static final String NETWORK_SCORE_SERVICE = "network_score"; + field public static final String NETWORK_STACK_SERVICE = "network_stack"; field public static final String OEM_LOCK_SERVICE = "oem_lock"; field public static final String PERMISSION_SERVICE = "permission"; field public static final String PERSISTENT_DATA_BLOCK_SERVICE = "persistent_data_block"; @@ -4404,10 +4412,12 @@ package android.media.soundtrigger { } public static class SoundTriggerManager.Model { - method public static android.media.soundtrigger.SoundTriggerManager.Model create(java.util.UUID, java.util.UUID, byte[]); - method public byte[] getModelData(); - method public java.util.UUID getModelUuid(); - method public java.util.UUID getVendorUuid(); + method @NonNull public static android.media.soundtrigger.SoundTriggerManager.Model create(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], int); + method @NonNull public static android.media.soundtrigger.SoundTriggerManager.Model create(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[]); + method @Nullable public byte[] getModelData(); + method @NonNull public java.util.UUID getModelUuid(); + method @NonNull public java.util.UUID getVendorUuid(); + method public int getVersion(); } } @@ -4537,6 +4547,7 @@ package android.media.tv { method @RequiresPermission(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS) public void addBlockedRating(@NonNull android.media.tv.TvContentRating); method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public boolean captureFrame(String, android.view.Surface, android.media.tv.TvStreamConfig); method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public java.util.List<android.media.tv.TvStreamConfig> getAvailableTvStreamConfigList(String); + method @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS) public int getClientPid(@NonNull String); method @NonNull @RequiresPermission("android.permission.DVB_DEVICE") public java.util.List<android.media.tv.DvbDeviceInfo> getDvbDeviceList(); method @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE) public java.util.List<android.media.tv.TvInputHardwareInfo> getHardwareList(); method @RequiresPermission(android.Manifest.permission.READ_CONTENT_RATING_SYSTEMS) public java.util.List<android.media.tv.TvContentRatingSystemInfo> getTvContentRatingSystemList(); @@ -4548,6 +4559,7 @@ package android.media.tv { method @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE) public void releaseTvInputHardware(int, android.media.tv.TvInputManager.Hardware); method @RequiresPermission(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS) public void removeBlockedRating(@NonNull android.media.tv.TvContentRating); method @RequiresPermission(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS) public void setParentalControlsEnabled(boolean); + field public static final int UNKNOWN_CLIENT_PID = -1; // 0xffffffff } public static final class TvInputManager.Hardware { @@ -4681,10 +4693,30 @@ package android.media.tv.tuner { package android.media.tv.tuner.filter { + public abstract class FilterConfiguration { + field public static final int FILTER_TYPE_ALP = 16; // 0x10 + field public static final int FILTER_TYPE_IP = 4; // 0x4 + field public static final int FILTER_TYPE_MMTP = 2; // 0x2 + field public static final int FILTER_TYPE_TLV = 8; // 0x8 + field public static final int FILTER_TYPE_TS = 1; // 0x1 + } + public abstract class FilterEvent { ctor public FilterEvent(); } + public class PesSettings extends android.media.tv.tuner.filter.Settings { + method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.filter.PesSettings.Builder builder(@NonNull android.content.Context, int); + method public int getStreamId(); + method public boolean isRaw(); + } + + public static class PesSettings.Builder { + method @NonNull public android.media.tv.tuner.filter.PesSettings build(); + method @NonNull public android.media.tv.tuner.filter.PesSettings.Builder setRaw(boolean); + method @NonNull public android.media.tv.tuner.filter.PesSettings.Builder setStreamId(int); + } + public class SectionEvent extends android.media.tv.tuner.filter.FilterEvent { method public int getDataLength(); method public int getSectionNumber(); @@ -4692,6 +4724,22 @@ package android.media.tv.tuner.filter { method public int getVersion(); } + public abstract class Settings { + } + + public class TsFilterConfiguration extends android.media.tv.tuner.filter.FilterConfiguration { + method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.filter.TsFilterConfiguration.Builder builder(@NonNull android.content.Context); + method @Nullable public android.media.tv.tuner.filter.Settings getSettings(); + method public int getTpid(); + method public int getType(); + } + + public static class TsFilterConfiguration.Builder { + method @NonNull public android.media.tv.tuner.filter.TsFilterConfiguration build(); + method @NonNull public android.media.tv.tuner.filter.TsFilterConfiguration.Builder setSettings(@NonNull android.media.tv.tuner.filter.Settings); + method @NonNull public android.media.tv.tuner.filter.TsFilterConfiguration.Builder setTpid(int); + } + } package android.metrics { @@ -4742,7 +4790,9 @@ package android.net { public class CaptivePortal implements android.os.Parcelable { method public void logEvent(int, @NonNull String); + method public void reevaluateNetwork(); method public void useNetwork(); + field public static final int APP_REQUEST_REEVALUATION_REQUIRED = 100; // 0x64 field public static final int APP_RETURN_DISMISSED = 0; // 0x0 field public static final int APP_RETURN_UNWANTED = 1; // 0x1 field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2 @@ -4756,6 +4806,7 @@ package android.net { method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported(); method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public int registerNetworkProvider(@NonNull android.net.NetworkProvider); method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback); + method @Deprecated public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, int, int, @NonNull android.os.Handler); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_AIRPLANE_MODE, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public boolean shouldAvoidBadWifi(); method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(@NonNull android.net.Network, @NonNull android.os.Bundle); @@ -4772,6 +4823,8 @@ package android.net { field public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13; // 0xd field public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0 field public static final int TETHER_ERROR_PROVISION_FAILED = 11; // 0xb + field public static final int TYPE_NONE = -1; // 0xffffffff + field @Deprecated public static final int TYPE_WIFI_P2P = 13; // 0xd } public abstract static class ConnectivityManager.OnStartTetheringCallback { @@ -4907,12 +4960,57 @@ package android.net { field @NonNull public static final android.os.Parcelable.Creator<android.net.MatchAllNetworkSpecifier> CREATOR; } + public final class NattKeepalivePacketData extends android.net.KeepalivePacketData implements android.os.Parcelable { + ctor public NattKeepalivePacketData(@NonNull java.net.InetAddress, int, @NonNull java.net.InetAddress, int, @NonNull byte[]) throws android.net.InvalidPacketException; + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.NattKeepalivePacketData> CREATOR; + } + public class Network implements android.os.Parcelable { ctor public Network(@NonNull android.net.Network); method @NonNull public android.net.Network getPrivateDnsBypassingCopy(); field public final int netId; } + public abstract class NetworkAgent { + method public void onAddKeepalivePacketFilter(int, @NonNull android.net.KeepalivePacketData); + method public void onAutomaticReconnectDisabled(); + method public void onBandwidthUpdateRequested(); + method public void onNetworkUnwanted(); + method public void onRemoveKeepalivePacketFilter(int); + method public void onSaveAcceptUnvalidated(boolean); + method public void onSignalStrengthThresholdsUpdated(@NonNull int[]); + method public void onStartSocketKeepalive(int, int, @NonNull android.net.KeepalivePacketData); + method public void onStopSocketKeepalive(int); + method public void onValidationStatus(int, @Nullable String); + method public void sendLinkProperties(@NonNull android.net.LinkProperties); + method public void sendNetworkCapabilities(@NonNull android.net.NetworkCapabilities); + method public void sendNetworkScore(int); + method public void sendSocketKeepaliveEvent(int, int); + field public static final int VALIDATION_STATUS_NOT_VALID = 2; // 0x2 + field public static final int VALIDATION_STATUS_VALID = 1; // 0x1 + field @NonNull public final android.net.Network network; + field public final int providerId; + } + + public final class NetworkAgentConfig implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public String getSubscriberId(); + method public boolean isNat64DetectionEnabled(); + method public boolean isProvisioningNotificationEnabled(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkAgentConfig> CREATOR; + } + + public static class NetworkAgentConfig.Builder { + ctor public NetworkAgentConfig.Builder(); + method @NonNull public android.net.NetworkAgentConfig build(); + method @NonNull public android.net.NetworkAgentConfig.Builder disableNat64Detection(); + method @NonNull public android.net.NetworkAgentConfig.Builder disableProvisioningNotification(); + method @NonNull public android.net.NetworkAgentConfig.Builder setSubscriberId(@Nullable String); + } + public final class NetworkCapabilities implements android.os.Parcelable { method public boolean deduceRestrictedCapability(); method @NonNull public int[] getTransportTypes(); @@ -4951,6 +5049,10 @@ package android.net { method public abstract void onRequestScores(android.net.NetworkKey[]); } + public class NetworkRequest implements android.os.Parcelable { + method public boolean satisfiedBy(@Nullable android.net.NetworkCapabilities); + } + public static class NetworkRequest.Builder { method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkRequest.Builder setSignalStrength(int); } @@ -4971,6 +5073,9 @@ package android.net { field @Deprecated public static final String EXTRA_NETWORKS_TO_SCORE = "networksToScore"; field public static final String EXTRA_NEW_SCORER = "newScorer"; field @Deprecated public static final String EXTRA_PACKAGE_NAME = "packageName"; + field public static final int SCORE_FILTER_CURRENT_NETWORK = 1; // 0x1 + field public static final int SCORE_FILTER_NONE = 0; // 0x0 + field public static final int SCORE_FILTER_SCAN_RESULTS = 2; // 0x2 } public static interface NetworkScoreManager.NetworkScoreCallback { @@ -5054,6 +5159,10 @@ package android.net { field public final android.net.RssiCurve rssiCurve; } + public abstract class SocketKeepalive implements java.lang.AutoCloseable { + field public static final int SUCCESS = 0; // 0x0 + } + public final class StaticIpConfiguration implements android.os.Parcelable { ctor public StaticIpConfiguration(); ctor public StaticIpConfiguration(@Nullable android.net.StaticIpConfiguration); @@ -5087,6 +5196,10 @@ package android.net { field @NonNull public final String specifier; } + public final class TelephonyNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable { + method public boolean satisfiedBy(android.net.NetworkSpecifier); + } + public class TrafficStats { method public static void setThreadStatsTagApp(); method public static void setThreadStatsTagBackup(); @@ -5848,6 +5961,7 @@ package android.net.wifi { } public class ScanResult implements android.os.Parcelable { + ctor public ScanResult(); field public static final int CIPHER_CCMP = 3; // 0x3 field public static final int CIPHER_GCMP_256 = 4; // 0x4 field public static final int CIPHER_NONE = 0; // 0x0 @@ -5964,6 +6078,7 @@ package android.net.wifi { method @Deprecated public static boolean isMetered(@Nullable android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiInfo); method @Deprecated public boolean isNoInternetAccessExpected(); method @Deprecated public void setIpConfiguration(@Nullable android.net.IpConfiguration); + method @Deprecated public void setNetworkSelectionStatus(@NonNull android.net.wifi.WifiConfiguration.NetworkSelectionStatus); method @Deprecated public void setProxy(@NonNull android.net.IpConfiguration.ProxySettings, @NonNull android.net.ProxyInfo); field @Deprecated public static final int AP_BAND_2GHZ = 0; // 0x0 field @Deprecated public static final int AP_BAND_5GHZ = 1; // 0x1 @@ -6008,6 +6123,7 @@ package android.net.wifi { method @Deprecated public boolean getHasEverConnected(); method @Deprecated @Nullable public static String getNetworkDisableReasonString(int); method @Deprecated public int getNetworkSelectionDisableReason(); + method @Deprecated public int getNetworkSelectionStatus(); method @Deprecated @NonNull public String getNetworkStatusString(); method @Deprecated public boolean isNetworkEnabled(); method @Deprecated public boolean isNetworkPermanentlyDisabled(); @@ -6022,6 +6138,16 @@ package android.net.wifi { field @Deprecated public static final int DISABLED_NO_INTERNET_TEMPORARY = 4; // 0x4 field @Deprecated public static final int NETWORK_SELECTION_DISABLED_MAX = 10; // 0xa field @Deprecated public static final int NETWORK_SELECTION_ENABLE = 0; // 0x0 + field @Deprecated public static final int NETWORK_SELECTION_ENABLED = 0; // 0x0 + field @Deprecated public static final int NETWORK_SELECTION_PERMANENTLY_DISABLED = 2; // 0x2 + field @Deprecated public static final int NETWORK_SELECTION_TEMPORARY_DISABLED = 1; // 0x1 + } + + @Deprecated public static final class WifiConfiguration.NetworkSelectionStatus.Builder { + ctor @Deprecated public WifiConfiguration.NetworkSelectionStatus.Builder(); + method @Deprecated @NonNull public android.net.wifi.WifiConfiguration.NetworkSelectionStatus build(); + method @Deprecated @NonNull public android.net.wifi.WifiConfiguration.NetworkSelectionStatus.Builder setNetworkSelectionDisableReason(int); + method @Deprecated @NonNull public android.net.wifi.WifiConfiguration.NetworkSelectionStatus.Builder setNetworkSelectionStatus(int); } @Deprecated public static class WifiConfiguration.RecentFailure { @@ -6066,6 +6192,15 @@ package android.net.wifi { field public static final int INVALID_RSSI = -127; // 0xffffff81 } + public static final class WifiInfo.Builder { + ctor public WifiInfo.Builder(); + method @NonNull public android.net.wifi.WifiInfo build(); + method @NonNull public android.net.wifi.WifiInfo.Builder setBssid(@NonNull String); + method @NonNull public android.net.wifi.WifiInfo.Builder setNetworkId(int); + method @NonNull public android.net.wifi.WifiInfo.Builder setRssi(int); + method @NonNull public android.net.wifi.WifiInfo.Builder setSsid(@NonNull byte[]); + } + public class WifiManager { method @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE) public void addOnWifiUsabilityStatsListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.OnWifiUsabilityStatsListener); method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void allowAutojoin(int, boolean); @@ -6111,6 +6246,7 @@ package android.net.wifi { method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public byte[] retrieveSoftApBackupData(); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void save(@NonNull android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiManager.ActionListener); method @RequiresPermission(android.Manifest.permission.WIFI_SET_DEVICE_MOBILITY_STATE) public void setDeviceMobilityState(int); + method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setMacRandomizationSettingPasspointEnabled(@NonNull String, boolean); method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public boolean setSoftApConfiguration(@NonNull android.net.wifi.SoftApConfiguration); method @Deprecated @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public boolean setWifiApConfiguration(android.net.wifi.WifiConfiguration); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startEasyConnectAsConfiguratorInitiator(@NonNull String, int, int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.EasyConnectStatusCallback); @@ -6460,6 +6596,7 @@ package android.net.wifi.hotspot2 { public final class PasspointConfiguration implements android.os.Parcelable { method public boolean isAutoJoinEnabled(); + method public boolean isMacRandomizationEnabled(); } public abstract class ProvisioningCallback { @@ -7581,7 +7718,10 @@ package android.permission { public final class PermissionManager { method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) public int getRuntimePermissionsVersion(); method @NonNull public java.util.List<android.permission.PermissionManager.SplitPermissionInfo> getSplitPermissions(); + method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS) public void grantDefaultPermissionsToEnabledImsServices(@NonNull String[], @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); + method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS) public void grantDefaultPermissionsToEnabledTelephonyDataServices(@NonNull String[], @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS) public void grantDefaultPermissionsToLuiApp(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); + method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS) public void revokeDefaultPermissionsFromDisabledTelephonyDataServices(@NonNull String[], @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS) public void revokeDefaultPermissionsFromLuiApps(@NonNull String[], @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @RequiresPermission(android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) public void setRuntimePermissionsVersion(@IntRange(from=0) int); method @RequiresPermission(android.Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS) public void startOneTimePermissionSession(@NonNull String, long, int, int); @@ -7882,6 +8022,8 @@ package android.provider { field public static final int COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE = 5; // 0x5 field public static final int COLUMN_INDEX_XML_RES_RANK = 0; // 0x0 field public static final int COLUMN_INDEX_XML_RES_RESID = 1; // 0x1 + field public static final String DYNAMIC_INDEXABLES_RAW = "dynamic_indexables_raw"; + field public static final String DYNAMIC_INDEXABLES_RAW_PATH = "settings/dynamic_indexables_raw"; field public static final String INDEXABLES_RAW = "indexables_raw"; field public static final String[] INDEXABLES_RAW_COLUMNS; field public static final String INDEXABLES_RAW_PATH = "settings/indexables_raw"; @@ -7939,6 +8081,7 @@ package android.provider { method public String getType(android.net.Uri); method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues); method public android.database.Cursor query(android.net.Uri, String[], String, String[], String); + method @Nullable public android.database.Cursor queryDynamicRawData(@Nullable String[]); method public abstract android.database.Cursor queryNonIndexableKeys(String[]); method public abstract android.database.Cursor queryRawData(String[]); method @Nullable public android.database.Cursor querySliceUriPairs(); @@ -7964,6 +8107,7 @@ package android.provider { public static final class Settings.Global extends android.provider.Settings.NameValueTable { method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static boolean putString(@NonNull android.content.ContentResolver, @NonNull String, @Nullable String, @Nullable String, boolean); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static void resetToDefaults(@NonNull android.content.ContentResolver, @Nullable String); + field public static final String AIRPLANE_MODE_TOGGLEABLE_RADIOS = "airplane_mode_toggleable_radios"; field public static final String APP_STANDBY_ENABLED = "app_standby_enabled"; field public static final String AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES = "autofill_compat_mode_allowed_packages"; field public static final String CARRIER_APP_NAMES = "carrier_app_names"; @@ -7976,13 +8120,21 @@ package android.provider { field public static final String EUICC_UNSUPPORTED_COUNTRIES = "euicc_unsupported_countries"; field public static final String INSTALL_CARRIER_APP_NOTIFICATION_PERSISTENT = "install_carrier_app_notification_persistent"; field public static final String INSTALL_CARRIER_APP_NOTIFICATION_SLEEP_MILLIS = "install_carrier_app_notification_sleep_millis"; + field public static final String NETWORK_RECOMMENDATIONS_ENABLED = "network_recommendations_enabled"; field public static final String OTA_DISABLE_AUTOMATIC_UPDATE = "ota_disable_automatic_update"; field public static final String REQUIRE_PASSWORD_TO_DECRYPT = "require_password_to_decrypt"; + field public static final String SOFT_AP_TIMEOUT_ENABLED = "soft_ap_timeout_enabled"; field public static final String TETHER_OFFLOAD_DISABLED = "tether_offload_disabled"; field public static final String TETHER_SUPPORTED = "tether_supported"; field public static final String THEATER_MODE_ON = "theater_mode_on"; field public static final String WEBVIEW_MULTIPROCESS = "webview_multiprocess"; field public static final String WIFI_BADGING_THRESHOLDS = "wifi_badging_thresholds"; + field public static final String WIFI_P2P_DEVICE_NAME = "wifi_p2p_device_name"; + field public static final String WIFI_P2P_PENDING_FACTORY_RESET = "wifi_p2p_pending_factory_reset"; + field public static final String WIFI_SCAN_ALWAYS_AVAILABLE = "wifi_scan_always_enabled"; + field public static final String WIFI_SCAN_THROTTLE_ENABLED = "wifi_scan_throttle_enabled"; + field public static final String WIFI_SCORE_PARAMS = "wifi_score_params"; + field public static final String WIFI_VERBOSE_LOGGING_ENABLED = "wifi_verbose_logging_enabled"; field public static final String WIFI_WAKEUP_ENABLED = "wifi_wakeup_enabled"; } @@ -8023,6 +8175,10 @@ package android.provider { field public static final int VOLUME_HUSH_VIBRATE = 1; // 0x1 } + public static final class Settings.System extends android.provider.Settings.NameValueTable { + method @RequiresPermission(android.Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE) public static boolean putString(@NonNull android.content.ContentResolver, @NonNull String, @Nullable String, boolean); + } + public static interface Telephony.CarrierColumns extends android.provider.BaseColumns { field @NonNull public static final android.net.Uri CONTENT_URI; field public static final String EXPIRATION_TIME = "expiration_time"; @@ -9334,6 +9490,11 @@ package android.telephony { field public static final int TRANSPORT_TYPE_WWAN = 1; // 0x1 } + public final class BarringInfo implements android.os.Parcelable { + ctor public BarringInfo(); + method @NonNull public android.telephony.BarringInfo createLocationInfoSanitizedCopy(); + } + public final class CallAttributes implements android.os.Parcelable { ctor public CallAttributes(@NonNull android.telephony.PreciseCallState, int, @NonNull android.telephony.CallQuality); method public int describeContents(); @@ -9346,6 +9507,7 @@ package android.telephony { public final class CallQuality implements android.os.Parcelable { ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int); + ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int, boolean, boolean, boolean); method public int describeContents(); method public int getAverageRelativeJitter(); method public int getAverageRoundTripTime(); @@ -9358,6 +9520,9 @@ package android.telephony { method public int getNumRtpPacketsTransmitted(); method public int getNumRtpPacketsTransmittedLost(); method public int getUplinkCallQualityLevel(); + method public boolean isIncomingSilenceDetected(); + method public boolean isOutgoingSilenceDetected(); + method public boolean isRtpInactivityDetected(); method public void writeToParcel(android.os.Parcel, int); field public static final int CALL_QUALITY_BAD = 4; // 0x4 field public static final int CALL_QUALITY_EXCELLENT = 0; // 0x0 @@ -10211,7 +10376,9 @@ package android.telephony { public class ServiceState implements android.os.Parcelable { method @NonNull public android.telephony.ServiceState createLocationInfoSanitizedCopy(boolean); method public void fillInNotifierBundle(@NonNull android.os.Bundle); + method public int getDataNetworkType(); method public int getDataRegistrationState(); + method public boolean getDataRoamingFromRegistration(); method @Nullable public android.telephony.NetworkRegistrationInfo getNetworkRegistrationInfo(int, int); method @NonNull public java.util.List<android.telephony.NetworkRegistrationInfo> getNetworkRegistrationInfoList(); method @NonNull public java.util.List<android.telephony.NetworkRegistrationInfo> getNetworkRegistrationInfoListForDomain(int); @@ -10357,9 +10524,20 @@ package android.telephony { method public boolean disableCellBroadcastRange(int, int, int); method public boolean enableCellBroadcastRange(int, int, int); method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_MESSAGES_ON_ICC) public java.util.List<android.telephony.SmsMessage> getMessagesFromIcc(); + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getPremiumSmsConsent(@NonNull String); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSmsCapacityOnIcc(); method public void sendMultipartTextMessage(@NonNull String, @NonNull String, @NonNull java.util.List<java.lang.String>, @Nullable java.util.List<android.app.PendingIntent>, @Nullable java.util.List<android.app.PendingIntent>, @NonNull String); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void sendMultipartTextMessageWithoutPersisting(String, String, java.util.List<java.lang.String>, java.util.List<android.app.PendingIntent>, java.util.List<android.app.PendingIntent>); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setPremiumSmsConsent(@NonNull String, int); + field public static final int PREMIUM_SMS_CONSENT_ALWAYS_ALLOW = 3; // 0x3 + field public static final int PREMIUM_SMS_CONSENT_ASK_USER = 1; // 0x1 + field public static final int PREMIUM_SMS_CONSENT_NEVER_ALLOW = 2; // 0x2 + field public static final int PREMIUM_SMS_CONSENT_UNKNOWN = 0; // 0x0 + } + + public class SmsMessage { + method @Nullable public static android.telephony.SmsMessage createFromNativeSmsSubmitPdu(@NonNull byte[], boolean); + method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public static byte[] getSubmitPduEncodedMessage(boolean, @NonNull String, @NonNull String, int, int, int, int, int, int); } public class SubscriptionInfo implements android.os.Parcelable { @@ -10457,6 +10635,7 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String getCdmaMin(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String getCdmaMin(int); method public String getCdmaPrlVersion(); + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getCdmaRoamingMode(); method public int getCurrentPhoneType(); method public int getCurrentPhoneType(int); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getDataActivationState(); @@ -10530,6 +10709,8 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setAlwaysAllowMmsData(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCarrierDataEnabled(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setCarrierRestrictionRules(@NonNull android.telephony.CarrierRestrictionRules); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setCdmaRoamingMode(int); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setCdmaSubscriptionMode(int); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataActivationState(int); method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(int, boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataRoamingEnabled(boolean); @@ -10573,6 +10754,10 @@ package android.telephony { field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1 field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0 field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff + field public static final int CDMA_SUBSCRIPTION_NV = 1; // 0x1 + field public static final int CDMA_SUBSCRIPTION_RUIM_SIM = 0; // 0x0 + field public static final int CDMA_SUBSCRIPTION_UNKNOWN = -1; // 0xffffffff + field public static final int DEFAULT_PREFERRED_NETWORK_MODE = 0; // 0x0 field public static final String EXTRA_ANOMALY_DESCRIPTION = "android.telephony.extra.ANOMALY_DESCRIPTION"; field public static final String EXTRA_ANOMALY_ID = "android.telephony.extra.ANOMALY_ID"; field @Deprecated public static final String EXTRA_APN_PROTOCOL = "apnProto"; @@ -10583,6 +10768,8 @@ package android.telephony { field public static final String EXTRA_ERROR_CODE = "errorCode"; field public static final String EXTRA_PCO_ID = "pcoId"; field public static final String EXTRA_PCO_VALUE = "pcoValue"; + field public static final String EXTRA_PHONE_IN_ECM_STATE = "android.telephony.extra.PHONE_IN_ECM_STATE"; + field public static final String EXTRA_PHONE_IN_EMERGENCY_CALL = "android.telephony.extra.PHONE_IN_EMERGENCY_CALL"; field public static final String EXTRA_REDIRECTION_URL = "redirectionUrl"; field public static final String EXTRA_SIM_STATE = "android.telephony.extra.SIM_STATE"; field public static final String EXTRA_VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL = "android.telephony.extra.VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL"; @@ -10610,6 +10797,7 @@ package android.telephony { field public static final long NETWORK_TYPE_BITMASK_TD_SCDMA = 65536L; // 0x10000L field public static final long NETWORK_TYPE_BITMASK_UMTS = 4L; // 0x4L field public static final long NETWORK_TYPE_BITMASK_UNKNOWN = 0L; // 0x0L + field public static final int PHONE_TYPE_THIRD_PARTY = 4; // 0x4 field public static final int RADIO_POWER_OFF = 0; // 0x0 field public static final int RADIO_POWER_ON = 1; // 0x1 field public static final int RADIO_POWER_UNAVAILABLE = 2; // 0x2 @@ -10633,6 +10821,7 @@ package android.telephony { public class TelephonyRegistryManager { method public void addOnOpportunisticSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener, @NonNull java.util.concurrent.Executor); method public void addOnSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnSubscriptionsChangedListener, @NonNull java.util.concurrent.Executor); + method public void notifyBarringInfoChanged(int, int, @NonNull android.telephony.BarringInfo); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyCallStateChangedForAllSubscriptions(int, @Nullable String); method public void notifyCarrierNetworkChange(boolean); method public void notifyRegistrationFailed(int, int, @NonNull android.telephony.CellIdentity, @NonNull String, int, int, int); @@ -11483,10 +11672,13 @@ package android.telephony.ims { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public int getProvisioningIntValue(int); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean getProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int); method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public String getProvisioningStringValue(int); + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean getRcsProvisioningStatusForCapability(int); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyRcsAutoConfigurationReceived(@NonNull byte[], boolean); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback) throws android.telephony.ims.ImsException; method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningIntValue(int, int); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, @NonNull String); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, boolean); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback); field public static final int KEY_EAB_PROVISIONING_STATUS = 25; // 0x19 field public static final int KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC = 19; // 0x13 @@ -11746,6 +11938,7 @@ package android.telephony.ims.stub { method public String getConfigString(int); method public final void notifyProvisionedValueChanged(int, int); method public final void notifyProvisionedValueChanged(int, String); + method public void notifyRcsAutoConfigurationReceived(@NonNull byte[], boolean); method public int setConfig(int, int); method public int setConfig(int, String); field public static final int CONFIG_RESULT_FAILED = 1; // 0x1 @@ -11961,7 +12154,28 @@ package android.util { method public int getUid(); } + public final class StatsEvent { + method @NonNull public static android.util.StatsEvent.Builder newBuilder(); + } + + public static final class StatsEvent.Builder { + method @NonNull public android.util.StatsEvent.Builder addBooleanAnnotation(byte, boolean); + method @NonNull public android.util.StatsEvent.Builder addIntAnnotation(byte, int); + method @NonNull public android.util.StatsEvent build(); + method @NonNull public android.util.StatsEvent.Builder setAtomId(int); + method @NonNull public android.util.StatsEvent.Builder usePooledBuffer(); + method @NonNull public android.util.StatsEvent.Builder writeAttributionChain(@NonNull int[], @NonNull String[]); + method @NonNull public android.util.StatsEvent.Builder writeBoolean(boolean); + method @NonNull public android.util.StatsEvent.Builder writeByteArray(@NonNull byte[]); + method @NonNull public android.util.StatsEvent.Builder writeFloat(float); + method @NonNull public android.util.StatsEvent.Builder writeInt(int); + method @NonNull public android.util.StatsEvent.Builder writeKeyValuePairs(@Nullable android.util.SparseIntArray, @Nullable android.util.SparseLongArray, @Nullable android.util.SparseArray<java.lang.String>, @Nullable android.util.SparseArray<java.lang.Float>); + method @NonNull public android.util.StatsEvent.Builder writeLong(long); + method @NonNull public android.util.StatsEvent.Builder writeString(@NonNull String); + } + public final class StatsLog { + method public static void write(@NonNull android.util.StatsEvent); method public static void writeRaw(@NonNull byte[], int); } @@ -12123,6 +12337,12 @@ package android.webkit { method public void onJsResultComplete(android.webkit.JsResult); } + public interface PacProcessor { + method @NonNull public static android.webkit.PacProcessor getInstance(); + method @Nullable public String makeProxyRequest(@NonNull String); + method public boolean setProxyScript(@NonNull String); + } + public class SslErrorHandler extends android.os.Handler { ctor public SslErrorHandler(); } @@ -12257,6 +12477,7 @@ package android.webkit { method public android.webkit.WebViewProvider createWebView(android.webkit.WebView, android.webkit.WebView.PrivateAccess); method public android.webkit.CookieManager getCookieManager(); method public android.webkit.GeolocationPermissions getGeolocationPermissions(); + method @NonNull public default android.webkit.PacProcessor getPacProcessor(); method public android.webkit.ServiceWorkerController getServiceWorkerController(); method public android.webkit.WebViewFactoryProvider.Statics getStatics(); method @Deprecated public android.webkit.TokenBindingService getTokenBindingService(); diff --git a/api/test-current.txt b/api/test-current.txt index 3a9b87eb30da..28119e3c120b 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -73,6 +73,7 @@ package android.app { method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void removeOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener); method public static void resumeAppSwitches() throws android.os.RemoteException; method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int); + method @RequiresPermission("android.permission.MANAGE_USERS") public boolean switchUser(@NonNull android.os.UserHandle); field public static final int PROCESS_CAPABILITY_ALL = 1; // 0x1 field public static final int PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1; // 0x1 field public static final int PROCESS_CAPABILITY_NONE = 0; // 0x0 @@ -471,7 +472,10 @@ package android.app { } public class WallpaperManager { + method @Nullable public android.graphics.Bitmap getBitmap(); method @RequiresPermission("android.permission.SET_WALLPAPER_COMPONENT") public boolean setWallpaperComponent(android.content.ComponentName); + method public boolean shouldEnableWideColorGamut(); + method @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public boolean wallpaperSupportsWcg(int); } public class WindowConfiguration implements java.lang.Comparable<android.app.WindowConfiguration> android.os.Parcelable { @@ -753,10 +757,12 @@ package android.content { method public int getUserId(); method public void setAutofillOptions(@Nullable android.content.AutofillOptions); method public void setContentCaptureOptions(@Nullable android.content.ContentCaptureOptions); + method @RequiresPermission("android.permission.INTERACT_ACROSS_USERS") public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle); field public static final String BLOB_STORE_SERVICE = "blob_store"; field public static final String BUGREPORT_SERVICE = "bugreport"; field public static final String CONTENT_CAPTURE_MANAGER_SERVICE = "content_capture"; field public static final String DEVICE_IDLE_CONTROLLER = "deviceidle"; + field public static final String NETWORK_STACK_SERVICE = "network_stack"; field public static final String PERMISSION_SERVICE = "permission"; field public static final String POWER_WHITELIST_MANAGER = "power_whitelist"; field public static final String ROLLBACK_SERVICE = "rollback"; @@ -1497,7 +1503,9 @@ package android.net { public class CaptivePortal implements android.os.Parcelable { method public void logEvent(int, @NonNull String); + method public void reevaluateNetwork(); method public void useNetwork(); + field public static final int APP_REQUEST_REEVALUATION_REQUIRED = 100; // 0x64 field public static final int APP_RETURN_DISMISSED = 0; // 0x0 field public static final int APP_RETURN_UNWANTED = 1; // 0x1 field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2 @@ -2506,10 +2514,12 @@ package android.provider { method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static float getFloat(@NonNull String, @NonNull String, float); method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static int getInt(@NonNull String, @NonNull String, int); method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static long getLong(@NonNull String, @NonNull String, long); + method @NonNull @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static android.provider.DeviceConfig.Properties getProperties(@NonNull String, @NonNull java.lang.String...); method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static String getProperty(@NonNull String, @NonNull String); method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static String getString(@NonNull String, @NonNull String, @Nullable String); method public static void removeOnPropertiesChangedListener(@NonNull android.provider.DeviceConfig.OnPropertiesChangedListener); method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static void resetToDefaults(int, @Nullable String); + method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperties(@NonNull android.provider.DeviceConfig.Properties) throws android.provider.DeviceConfig.BadConfigException; method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperty(@NonNull String, @NonNull String, @Nullable String, boolean); field public static final String NAMESPACE_ANDROID = "android"; field public static final String NAMESPACE_AUTOFILL = "autofill"; @@ -2521,6 +2531,10 @@ package android.provider { field public static final String NAMESPACE_ROLLBACK_BOOT = "rollback_boot"; } + public static class DeviceConfig.BadConfigException extends java.lang.Exception { + ctor public DeviceConfig.BadConfigException(); + } + public static interface DeviceConfig.OnPropertiesChangedListener { method public void onPropertiesChanged(@NonNull android.provider.DeviceConfig.Properties); } @@ -3131,8 +3145,18 @@ package android.telephony { field public static final int TRANSPORT_TYPE_WWAN = 1; // 0x1 } + public final class BarringInfo implements android.os.Parcelable { + ctor public BarringInfo(); + ctor public BarringInfo(@Nullable android.telephony.CellIdentity, @NonNull android.util.SparseArray<android.telephony.BarringInfo.BarringServiceInfo>); + } + + public static final class BarringInfo.BarringServiceInfo implements android.os.Parcelable { + ctor public BarringInfo.BarringServiceInfo(int, boolean, int, int); + } + public final class CallQuality implements android.os.Parcelable { ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int); + ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int, boolean, boolean, boolean); method public int describeContents(); method public int getAverageRelativeJitter(); method public int getAverageRoundTripTime(); @@ -3145,6 +3169,9 @@ package android.telephony { method public int getNumRtpPacketsTransmitted(); method public int getNumRtpPacketsTransmittedLost(); method public int getUplinkCallQualityLevel(); + method public boolean isIncomingSilenceDetected(); + method public boolean isOutgoingSilenceDetected(); + method public boolean isRtpInactivityDetected(); method public void writeToParcel(android.os.Parcel, int); field public static final int CALL_QUALITY_BAD = 4; // 0x4 field public static final int CALL_QUALITY_EXCELLENT = 0; // 0x0 @@ -3257,6 +3284,7 @@ package android.telephony { public class ServiceState implements android.os.Parcelable { method public void addNetworkRegistrationInfo(android.telephony.NetworkRegistrationInfo); + method public int getDataNetworkType(); method public void setCdmaSystemAndNetworkId(int, int); method public void setCellBandwidths(int[]); method public void setChannelNumber(int); @@ -3819,10 +3847,13 @@ package android.telephony.ims { method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @WorkerThread public int getProvisioningIntValue(int); method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @WorkerThread public boolean getProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int); method @Nullable @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @WorkerThread public String getProvisioningStringValue(int); + method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @WorkerThread public boolean getRcsProvisioningStatusForCapability(int); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyRcsAutoConfigurationReceived(@NonNull byte[], boolean); method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void registerProvisioningChangedCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback) throws android.telephony.ims.ImsException; method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningIntValue(int, int); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, @NonNull String); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, boolean); method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback); field public static final int KEY_EAB_PROVISIONING_STATUS = 25; // 0x19 field public static final int KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC = 19; // 0x13 @@ -4041,6 +4072,7 @@ package android.telephony.ims.stub { method public String getConfigString(int); method public final void notifyProvisionedValueChanged(int, int); method public final void notifyProvisionedValueChanged(int, String); + method public void notifyRcsAutoConfigurationReceived(@NonNull byte[], boolean); method public int setConfig(int, int); method public int setConfig(int, String); field public static final int CONFIG_RESULT_FAILED = 1; // 0x1 @@ -4270,6 +4302,7 @@ package android.util { field public static final String FFLAG_OVERRIDE_PREFIX = "sys.fflag.override."; field public static final String FFLAG_PREFIX = "sys.fflag."; field public static final String HEARING_AID_SETTINGS = "settings_bluetooth_hearing_aid"; + field public static final String NOTIF_CONVO_BYPASS_SHORTCUT_REQ = "settings_notif_convo_bypass_shortcut_req"; field public static final String PERSIST_PREFIX = "persist.sys.fflag.override."; field public static final String SCREENRECORD_LONG_PRESS = "settings_screenrecord_long_press"; field public static final String SEAMLESS_TRANSFER = "settings_seamless_transfer"; @@ -4369,6 +4402,7 @@ package android.view { } public final class Display { + method @NonNull public android.graphics.ColorSpace[] getSupportedWideColorGamut(); method public boolean hasAccess(int); } @@ -4486,6 +4520,8 @@ package android.view.accessibility { } public class AccessibilityNodeInfo implements android.os.Parcelable { + method public void addChild(@NonNull android.os.IBinder); + method public void setLeashedParent(@Nullable android.os.IBinder, int); method public static void setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger); method public void writeToParcelNoRecycle(android.os.Parcel, int); } @@ -4644,6 +4680,18 @@ package android.view.contentcapture { package android.view.inputmethod { + public final class InlineSuggestion implements android.os.Parcelable { + method @NonNull public static android.view.inputmethod.InlineSuggestion newInlineSuggestion(@NonNull android.view.inputmethod.InlineSuggestionInfo); + } + + public final class InlineSuggestionInfo implements android.os.Parcelable { + method @NonNull public static android.view.inputmethod.InlineSuggestionInfo newInlineSuggestionInfo(@NonNull android.view.inline.InlinePresentationSpec, @NonNull String, @Nullable String[]); + } + + public final class InlineSuggestionsResponse implements android.os.Parcelable { + method @NonNull public static android.view.inputmethod.InlineSuggestionsResponse newInlineSuggestionsResponse(@NonNull java.util.List<android.view.inputmethod.InlineSuggestion>); + } + public final class InputMethodManager { method public int getDisplayId(); method public boolean isInputMethodPickerShown(); diff --git a/api/test-lint-baseline.txt b/api/test-lint-baseline.txt index 6d1f29122b71..603f7a259462 100644 --- a/api/test-lint-baseline.txt +++ b/api/test-lint-baseline.txt @@ -400,7 +400,7 @@ GetterSetterNames: android.location.GnssClock#setLeapSecond(int): GetterSetterNames: android.location.GnssClock#setTimeUncertaintyNanos(double): GetterSetterNames: android.location.GnssMeasurement#setBasebandCn0DbHz(double): - + GetterSetterNames: android.location.GnssMeasurement#setCarrierFrequencyHz(float): GetterSetterNames: android.location.GnssMeasurement#setCodeType(String): @@ -466,7 +466,7 @@ InternalField: android.telephony.ims.ImsConferenceState#mParticipants: KotlinOperator: android.os.WorkSource#get(int): KotlinOperator: android.util.SparseArrayMap#get(int, String): - Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object) + ListenerInterface: android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener: @@ -2059,6 +2059,18 @@ MissingNullability: android.view.FocusFinder#sort(android.view.View[], int, int, MissingNullability: android.view.KeyEvent#actionToString(int): +MissingNullability: android.view.SurfaceControlViewHost#SurfaceControlViewHost(android.content.Context, android.view.Display, android.view.SurfaceControl) parameter #0: + +MissingNullability: android.view.SurfaceControlViewHost#SurfaceControlViewHost(android.content.Context, android.view.Display, android.view.SurfaceControl) parameter #1: + +MissingNullability: android.view.SurfaceControlViewHost#SurfaceControlViewHost(android.content.Context, android.view.Display, android.view.SurfaceControl) parameter #2: + +MissingNullability: android.view.SurfaceControlViewHost#addView(android.view.View, android.view.WindowManager.LayoutParams) parameter #0: + +MissingNullability: android.view.SurfaceControlViewHost#addView(android.view.View, android.view.WindowManager.LayoutParams) parameter #1: + +MissingNullability: android.view.SurfaceControlViewHost#relayout(android.view.WindowManager.LayoutParams) parameter #0: + MissingNullability: android.view.View#getTooltipView(): MissingNullability: android.view.View#isDefaultFocusHighlightNeeded(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable) parameter #0: @@ -2079,18 +2091,6 @@ MissingNullability: android.view.ViewDebug#startRenderingCommandsCapture(android MissingNullability: android.view.WindowManager.LayoutParams#accessibilityTitle: -MissingNullability: android.view.SurfaceControlViewHost#SurfaceControlViewHost(android.content.Context, android.view.Display, android.view.SurfaceControl) parameter #0: - -MissingNullability: android.view.SurfaceControlViewHost#SurfaceControlViewHost(android.content.Context, android.view.Display, android.view.SurfaceControl) parameter #1: - -MissingNullability: android.view.SurfaceControlViewHost#SurfaceControlViewHost(android.content.Context, android.view.Display, android.view.SurfaceControl) parameter #2: - -MissingNullability: android.view.SurfaceControlViewHost#addView(android.view.View, android.view.WindowManager.LayoutParams) parameter #0: - -MissingNullability: android.view.SurfaceControlViewHost#addView(android.view.View, android.view.WindowManager.LayoutParams) parameter #1: - -MissingNullability: android.view.SurfaceControlViewHost#relayout(android.view.WindowManager.LayoutParams) parameter #0: - MissingNullability: android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener#onAccessibilityServicesStateChanged(android.view.accessibility.AccessibilityManager) parameter #0: MissingNullability: android.view.accessibility.AccessibilityNodeInfo#setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger) parameter #0: @@ -2426,11 +2426,13 @@ ProtectedMember: android.view.View#resetResolvedDrawables(): ProtectedMember: android.view.ViewGroup#resetResolvedDrawables(): -PublicTypedef: android.os.HwParcel.Status: Don't expose @IntDef: @Status must be hidden. -PublicTypedef: android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability: Don't expose @IntDef: @MmTelCapability must be hidden. - -PublicTypedef: android.telephony.ims.feature.MmTelFeature.ProcessCallResult: Don't expose @IntDef: @ProcessCallResult must be hidden. +PublicTypedef: android.os.HwParcel.Status: + +PublicTypedef: android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability: + +PublicTypedef: android.telephony.ims.feature.MmTelFeature.ProcessCallResult: + RawAidl: android.telephony.mbms.vendor.MbmsDownloadServiceBase: @@ -2513,6 +2515,8 @@ SamShouldBeLast: android.database.sqlite.SQLiteDebug#dump(android.util.Printer, SamShouldBeLast: android.database.sqlite.SQLiteDirectCursorDriver#query(android.database.sqlite.SQLiteDatabase.CursorFactory, String[]): +SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(android.location.LocationRequest, android.location.LocationListener, android.os.Looper): + SAM-compatible parameters (such as parameter 2, "listener", in android.location.LocationManager.requestLocationUpdates) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(android.location.LocationRequest, java.util.concurrent.Executor, android.location.LocationListener): SamShouldBeLast: android.os.BugreportManager#startBugreport(android.os.ParcelFileDescriptor, android.os.ParcelFileDescriptor, android.os.BugreportParams, java.util.concurrent.Executor, android.os.BugreportManager.BugreportCallback): @@ -2593,6 +2597,8 @@ UseParcelFileDescriptor: android.util.proto.ProtoOutputStream#ProtoOutputStream( +UserHandle: android.app.ActivityManager#switchUser(android.os.UserHandle): + When a method overload is needed to target a specific UserHandle, callers should be directed to use Context.createPackageContextAsUser() and re-obtain the relevant Manager, and no new API should be added UserHandle: android.app.admin.DevicePolicyManager#getOwnerInstalledCaCerts(android.os.UserHandle): UserHandle: android.app.role.RoleManager#addOnRoleHoldersChangedListenerAsUser(java.util.concurrent.Executor, android.app.role.OnRoleHoldersChangedListener, android.os.UserHandle): @@ -2607,8 +2613,12 @@ UserHandle: android.app.role.RoleManager#removeOnRoleHoldersChangedListenerAsUse UserHandle: android.app.role.RoleManager#removeRoleHolderAsUser(String, String, int, android.os.UserHandle, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Boolean>): -UserHandle: android.companion.CompanionDeviceManager#isDeviceAssociated(String, android.net.MacAddress, android.os.UserHandle): +UserHandle: android.app.usage.StorageStatsManager#queryCratesForPackage(java.util.UUID, String, android.os.UserHandle): + When a method overload is needed to target a specific UserHandle, callers should be directed to use Context.createPackageContextAsUser() and re-obtain the relevant Manager, and no new API should be added +UserHandle: android.app.usage.StorageStatsManager#queryCratesForUser(java.util.UUID, android.os.UserHandle): When a method overload is needed to target a specific UserHandle, callers should be directed to use Context.createPackageContextAsUser() and re-obtain the relevant Manager, and no new API should be added +UserHandle: android.companion.CompanionDeviceManager#isDeviceAssociated(String, android.net.MacAddress, android.os.UserHandle): + UserHandle: android.content.pm.PackageManager#getInstallReason(String, android.os.UserHandle): UserHandle: android.content.pm.PackageManager#getPermissionFlags(String, String, android.os.UserHandle): diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 459520a3eb27..8fac31a05c49 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -1113,7 +1113,7 @@ void BootAnimation::handleViewport(nsecs_t timestep) { SurfaceComposerClient::Transaction t; t.setPosition(mFlingerSurfaceControl, 0, -mTargetInset) .setCrop(mFlingerSurfaceControl, Rect(0, mTargetInset, mWidth, mHeight)); - t.setDisplayProjection(mDisplayToken, 0 /* orientation */, layerStackRect, displayRect); + t.setDisplayProjection(mDisplayToken, ui::ROTATION_0, layerStackRect, displayRect); t.apply(); mTargetInset = mCurrentInset = 0; diff --git a/cmds/incident_helper/Android.bp b/cmds/incident_helper/Android.bp index d7b6d69ade74..64f4c667820d 100644 --- a/cmds/incident_helper/Android.bp +++ b/cmds/incident_helper/Android.bp @@ -1,3 +1,28 @@ +// Copyright (C) 2017 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. + +java_binary { + name: "incident-helper-cmd", + wrapper: "incident_helper_cmd", + srcs: [ + "java/**/*.java", + ], + proto: { + plugin: "javastream", + }, +} + cc_defaults { name: "incident_helper_defaults", diff --git a/cmds/incident_helper/incident_helper_cmd b/cmds/incident_helper/incident_helper_cmd new file mode 100644 index 000000000000..d45f7df41aaf --- /dev/null +++ b/cmds/incident_helper/incident_helper_cmd @@ -0,0 +1,6 @@ +#!/system/bin/sh +# Script to start "incident_helper_cmd" on the device +# +base=/system +export CLASSPATH=$base/framework/incident-helper-cmd.jar +exec app_process $base/bin com.android.commands.incident.IncidentHelper "$@" diff --git a/cmds/incident_helper/java/com/android/commands/incident/ExecutionException.java b/cmds/incident_helper/java/com/android/commands/incident/ExecutionException.java new file mode 100644 index 000000000000..d97b17ea630b --- /dev/null +++ b/cmds/incident_helper/java/com/android/commands/incident/ExecutionException.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.commands.incident; + +/** + * Thrown when there is an error executing a section. + */ +public class ExecutionException extends Exception { + /** + * Constructs a ExecutionException. + * + * @param msg the message + */ + public ExecutionException(String msg) { + super(msg); + } + + /** + * Constructs a ExecutionException from another exception. + * + * @param e the exception + */ + public ExecutionException(Exception e) { + super(e); + } +} diff --git a/cmds/incident_helper/java/com/android/commands/incident/IncidentHelper.java b/cmds/incident_helper/java/com/android/commands/incident/IncidentHelper.java new file mode 100644 index 000000000000..e5874e0a5201 --- /dev/null +++ b/cmds/incident_helper/java/com/android/commands/incident/IncidentHelper.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.commands.incident; + +import android.util.Log; + +import com.android.commands.incident.sections.PersistLogSection; + +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.ListIterator; + +/** + * Helper command runner for incidentd to run customized command to gather data for a non-standard + * section. + */ +public class IncidentHelper { + private static final String TAG = "IncidentHelper"; + private static boolean sLog = false; + private final List<String> mArgs; + private ListIterator<String> mArgsIterator; + + private IncidentHelper(String[] args) { + mArgs = Collections.unmodifiableList(Arrays.asList(args)); + mArgsIterator = mArgs.listIterator(); + } + + private static void showUsage(PrintStream out) { + out.println("This command is not designed to be run manually."); + out.println("Usage:"); + out.println(" run [sectionName]"); + } + + private void run(String[] args) throws ExecutionException { + Section section = null; + List<String> sectionArgs = new ArrayList<>(); + while (mArgsIterator.hasNext()) { + String arg = mArgsIterator.next(); + if ("-l".equals(arg)) { + sLog = true; + Log.i(TAG, "Args: [" + String.join(",", args) + "]"); + } else if ("run".equals(arg)) { + section = getSection(nextArgRequired()); + mArgsIterator.forEachRemaining(sectionArgs::add); + break; + } else { + log(Log.WARN, TAG, "Error: Unknown argument: " + arg); + return; + } + } + section.run(System.in, System.out, sectionArgs); + } + + private static Section getSection(String name) throws IllegalArgumentException { + if ("persisted_logs".equals(name)) { + return new PersistLogSection(); + } + throw new IllegalArgumentException("Section not found: " + name); + } + + private String nextArgRequired() { + if (!mArgsIterator.hasNext()) { + throw new IllegalArgumentException( + "Arg required after \"" + mArgs.get(mArgsIterator.previousIndex()) + "\""); + } + return mArgsIterator.next(); + } + + /** + * Print the given message to stderr, also log it if asked to (set by -l cmd arg). + */ + public static void log(int priority, String tag, String msg) { + System.err.println(tag + ": " + msg); + if (sLog) { + Log.println(priority, tag, msg); + } + } + + /** + * Command-line entry point. + * + * @param args The command-line arguments + */ + public static void main(String[] args) { + if (args.length == 0) { + showUsage(System.err); + System.exit(0); + } + IncidentHelper incidentHelper = new IncidentHelper(args); + try { + incidentHelper.run(args); + } catch (IllegalArgumentException e) { + showUsage(System.err); + System.err.println(); + e.printStackTrace(System.err); + if (sLog) { + Log.e(TAG, "Error: ", e); + } + } catch (Exception e) { + e.printStackTrace(System.err); + if (sLog) { + Log.e(TAG, "Error: ", e); + } + System.exit(1); + } + } +} diff --git a/cmds/incident_helper/java/com/android/commands/incident/Section.java b/cmds/incident_helper/java/com/android/commands/incident/Section.java new file mode 100644 index 000000000000..1c8c6578fb5e --- /dev/null +++ b/cmds/incident_helper/java/com/android/commands/incident/Section.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.commands.incident; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; + +/** Section interface used by {@link IncidentHelper}. */ +public interface Section { + /** + * Writes protobuf wire format to out, optionally reads data from in, with supplied args. + */ + void run(InputStream in, OutputStream out, List<String> args) throws ExecutionException; +} diff --git a/cmds/incident_helper/java/com/android/commands/incident/sections/PersistLogSection.java b/cmds/incident_helper/java/com/android/commands/incident/sections/PersistLogSection.java new file mode 100644 index 000000000000..f9d2e79e750a --- /dev/null +++ b/cmds/incident_helper/java/com/android/commands/incident/sections/PersistLogSection.java @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.commands.incident.sections; + +import android.util.Log; +import android.util.PersistedLogProto; +import android.util.TextLogEntry; +import android.util.proto.ProtoOutputStream; + +import com.android.commands.incident.ExecutionException; +import com.android.commands.incident.IncidentHelper; +import com.android.commands.incident.Section; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Comparator; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +/** PersistLogSection reads persisted logs and parses them into a PersistedLogProto. */ +public class PersistLogSection implements Section { + private static final String TAG = "IH_PersistLog"; + private static final String LOG_DIR = "/data/misc/logd/"; + // Persist log files are named logcat, logcat.001, logcat.002, logcat.003, ... + private static final Pattern LOG_FILE_RE = Pattern.compile("logcat(\\.\\d+)?"); + private static final Pattern BUFFER_BEGIN_RE = + Pattern.compile("--------- (?:beginning of|switch to) (.*)"); + private static final Map<String, Long> SECTION_NAME_TO_ID = new HashMap<>(); + private static final Map<Character, Integer> LOG_PRIORITY_MAP = new HashMap<>(); + private static final String DEFAULT_BUFFER = "main"; + + static { + SECTION_NAME_TO_ID.put("main", PersistedLogProto.MAIN_LOGS); + SECTION_NAME_TO_ID.put("radio", PersistedLogProto.RADIO_LOGS); + SECTION_NAME_TO_ID.put("events", PersistedLogProto.EVENTS_LOGS); + SECTION_NAME_TO_ID.put("system", PersistedLogProto.SYSTEM_LOGS); + SECTION_NAME_TO_ID.put("crash", PersistedLogProto.CRASH_LOGS); + SECTION_NAME_TO_ID.put("kernel", PersistedLogProto.KERNEL_LOGS); + } + + static { + LOG_PRIORITY_MAP.put('V', TextLogEntry.LOG_VERBOSE); + LOG_PRIORITY_MAP.put('D', TextLogEntry.LOG_DEBUG); + LOG_PRIORITY_MAP.put('I', TextLogEntry.LOG_INFO); + LOG_PRIORITY_MAP.put('W', TextLogEntry.LOG_WARN); + LOG_PRIORITY_MAP.put('E', TextLogEntry.LOG_ERROR); + LOG_PRIORITY_MAP.put('F', TextLogEntry.LOG_FATAL); + LOG_PRIORITY_MAP.put('S', TextLogEntry.LOG_SILENT); + } + + /** + * Caches dates at 00:00:00 to epoch second elapsed conversion. There are only a few different + * dates in persisted logs in one device, and constructing DateTime object is relatively + * expensive. + */ + private Map<Integer, Long> mEpochTimeCache = new HashMap<>(); + private ProtoOutputStream mProto; + private long mCurrFieldId; + private long mMaxBytes = Long.MAX_VALUE; + + @Override + public void run(InputStream in, OutputStream out, List<String> args) throws ExecutionException { + parseArgs(args); + Path logDirPath = Paths.get(LOG_DIR); + if (!Files.exists(logDirPath)) { + IncidentHelper.log(Log.WARN, TAG, "Skip dump. " + logDirPath + " does not exist."); + return; + } + if (!Files.isReadable(logDirPath)) { + IncidentHelper.log(Log.WARN, TAG, "Skip dump. " + logDirPath + " is not readable."); + return; + } + mProto = new ProtoOutputStream(out); + setCurrentSection(DEFAULT_BUFFER); + final Matcher logFileRe = LOG_FILE_RE.matcher(""); + // Need to process older log files first and write logs to proto in chronological order + // But we want to process only the latest ones if there is a size limit + try (Stream<File> stream = Files.list(logDirPath).map(Path::toFile) + .filter(f -> !f.isDirectory() && match(logFileRe, f.getName()) != null) + .sorted(Comparator.comparingLong(File::lastModified).reversed())) { + Iterator<File> iter = stream.iterator(); + List<File> filesToProcess = new ArrayList<>(); + long sumBytes = 0; + while (iter.hasNext()) { + File file = iter.next(); + sumBytes += file.length(); + if (sumBytes > mMaxBytes) { + break; + } + filesToProcess.add(file); + } + IncidentHelper.log(Log.INFO, TAG, "Limit # log files to " + filesToProcess.size()); + filesToProcess.stream() + .sorted(Comparator.comparingLong(File::lastModified)) + .forEachOrdered(this::processFile); + } catch (IOException e) { + throw new ExecutionException(e); + } finally { + mProto.flush(); + } + IncidentHelper.log(Log.DEBUG, TAG, "Bytes written: " + mProto.getBytes().length); + } + + private void parseArgs(List<String> args) { + Iterator<String> iter = args.iterator(); + while (iter.hasNext()) { + String arg = iter.next(); + if ("--limit".equals(arg) && iter.hasNext()) { + String sizeStr = iter.next().toLowerCase(); + if (sizeStr.endsWith("mb")) { + mMaxBytes = Long.parseLong(sizeStr.replace("mb", "")) * 1024 * 1024; + } else if (sizeStr.endsWith("kb")) { + mMaxBytes = Long.parseLong(sizeStr.replace("kb", "")) * 1024; + } else { + mMaxBytes = Long.parseLong(sizeStr); + } + } else { + throw new IllegalArgumentException("Unknown argument: " + arg); + } + } + } + + private void processFile(File file) { + final Matcher bufferBeginRe = BUFFER_BEGIN_RE.matcher(""); + try (BufferedReader reader = Files.newBufferedReader(file.toPath(), + StandardCharsets.UTF_8)) { + String line; + Matcher m; + while ((line = reader.readLine()) != null) { + if ((m = match(bufferBeginRe, line)) != null) { + setCurrentSection(m.group(1)); + continue; + } + parseLine(line); + } + } catch (IOException e) { + // Non-fatal error. We can skip and still process other files. + IncidentHelper.log(Log.WARN, TAG, "Error reading \"" + file + "\": " + e.getMessage()); + } + IncidentHelper.log(Log.DEBUG, TAG, "Finished reading " + file); + } + + private void setCurrentSection(String sectionName) { + Long sectionId = SECTION_NAME_TO_ID.get(sectionName); + if (sectionId == null) { + IncidentHelper.log(Log.WARN, TAG, "Section does not exist: " + sectionName); + sectionId = SECTION_NAME_TO_ID.get(DEFAULT_BUFFER); + } + mCurrFieldId = sectionId; + } + + /** + * Parse a log line in the following format: + * 01-01 15:01:47.723501 2738 2895 I Exp_TAG: example log line + * + * It does not use RegExp for performance reasons. Using this RegExp "(\\d{2})-(\\d{2})\\s + * (\\d{2}):(\\d{2}):(\\d{2}).(\\d{6})\\s+(\\d+)\\s+(\\d+)\\s+(.)\\s+(.*?):\\s(.*)" is twice as + * slow as the current approach. + */ + private void parseLine(String line) { + long token = mProto.start(mCurrFieldId); + try { + mProto.write(TextLogEntry.SEC, getEpochSec(line)); + // Nanosec is 15th to 20th digits of "10-01 02:57:27.710652" times 1000 + mProto.write(TextLogEntry.NANOSEC, parseInt(line, 15, 21) * 1000L); + + int start = nextNonBlank(line, 21); + int end = line.indexOf(' ', start + 1); + mProto.write(TextLogEntry.PID, parseInt(line, start, end)); + + start = nextNonBlank(line, end); + end = line.indexOf(' ', start + 1); + mProto.write(TextLogEntry.TID, parseInt(line, start, end)); + + start = nextNonBlank(line, end); + char priority = line.charAt(start); + mProto.write(TextLogEntry.PRIORITY, + LOG_PRIORITY_MAP.getOrDefault(priority, TextLogEntry.LOG_DEFAULT)); + + start = nextNonBlank(line, start + 1); + end = line.indexOf(": ", start); + mProto.write(TextLogEntry.TAG, line.substring(start, end).trim()); + mProto.write(TextLogEntry.LOG, line.substring(Math.min(end + 2, line.length()))); + } catch (RuntimeException e) { + // Error reporting is likely piped to /dev/null. Inserting it into the proto to make + // it more useful. + mProto.write(TextLogEntry.SEC, System.currentTimeMillis() / 1000); + mProto.write(TextLogEntry.PRIORITY, TextLogEntry.LOG_ERROR); + mProto.write(TextLogEntry.TAG, TAG); + mProto.write(TextLogEntry.LOG, + "Error parsing \"" + line + "\"" + ": " + e.getMessage()); + } + mProto.end(token); + } + + // ============== Below are util methods to parse log lines ============== + + private static int nextNonBlank(String line, int start) { + for (int i = start; i < line.length(); i++) { + if (line.charAt(i) != ' ') { + return i; + } + } + return -1; + } + + /** + * Gets the epoch second from the line string. Line starts with a fixed-length timestamp like + * "10-01 02:57:27.710652" + */ + private long getEpochSec(String line) { + int month = getDigit(line, 0) * 10 + getDigit(line, 1); + int day = getDigit(line, 3) * 10 + getDigit(line, 4); + + int mmdd = month * 100 + day; + long epochSecBase = mEpochTimeCache.computeIfAbsent(mmdd, (key) -> { + final GregorianCalendar calendar = new GregorianCalendar(); + calendar.set(Calendar.MONTH, (month + 12 - 1) % 12); + calendar.set(Calendar.DAY_OF_MONTH, day); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + // Date in log entries can never be in the future. If it happens, it means we are off + // by one year. + if (calendar.getTimeInMillis() > System.currentTimeMillis()) { + calendar.roll(Calendar.YEAR, /*amount=*/-1); + } + return calendar.getTimeInMillis() / 1000; + }); + + int hh = getDigit(line, 6) * 10 + getDigit(line, 7); + int mm = getDigit(line, 9) * 10 + getDigit(line, 10); + int ss = getDigit(line, 12) * 10 + getDigit(line, 13); + return epochSecBase + hh * 3600 + mm * 60 + ss; + } + + private static int parseInt(String line, /*inclusive*/ int start, /*exclusive*/ int end) { + int num = 0; + for (int i = start; i < end; i++) { + num = num * 10 + getDigit(line, i); + } + return num; + } + + private static int getDigit(String str, int pos) { + int digit = str.charAt(pos) - '0'; + if (digit < 0 || digit > 9) { + throw new NumberFormatException("'" + str.charAt(pos) + "' is not a digit."); + } + return digit; + } + + private static Matcher match(Matcher matcher, String text) { + matcher.reset(text); + return matcher.matches() ? matcher : null; + } +} diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp index cfd77c2357cd..62312d1cccaa 100644 --- a/cmds/incidentd/src/IncidentService.cpp +++ b/cmds/incidentd/src/IncidentService.cpp @@ -123,14 +123,17 @@ static string build_uri(const string& pkg, const string& cls, const string& id) // ================================================================================ ReportHandler::ReportHandler(const sp<WorkDirectory>& workDirectory, - const sp<Broadcaster>& broadcaster, const sp<Looper>& handlerLooper, - const sp<Throttler>& throttler) + const sp<Broadcaster>& broadcaster, + const sp<Looper>& handlerLooper, + const sp<Throttler>& throttler, + const vector<BringYourOwnSection*>& registeredSections) :mLock(), mWorkDirectory(workDirectory), mBroadcaster(broadcaster), mHandlerLooper(handlerLooper), mBacklogDelay(DEFAULT_DELAY_NS), mThrottler(throttler), + mRegisteredSections(registeredSections), mBatch(new ReportBatch()) { } @@ -185,7 +188,7 @@ void ReportHandler::take_report() { return; } - sp<Reporter> reporter = new Reporter(mWorkDirectory, batch); + sp<Reporter> reporter = new Reporter(mWorkDirectory, batch, mRegisteredSections); // Take the report, which might take a while. More requests might queue // up while we're doing this, and we'll handle them in their next batch. @@ -237,7 +240,7 @@ IncidentService::IncidentService(const sp<Looper>& handlerLooper) { mWorkDirectory = new WorkDirectory(); mBroadcaster = new Broadcaster(mWorkDirectory); mHandler = new ReportHandler(mWorkDirectory, mBroadcaster, handlerLooper, - mThrottler); + mThrottler, mRegisteredSections); mBroadcaster->setHandler(mHandler); } @@ -327,6 +330,11 @@ Status IncidentService::reportIncidentToDumpstate(unique_fd stream, incidentArgs.addSection(id); } } + for (const Section* section : mRegisteredSections) { + if (!section_requires_specific_mention(section->id)) { + incidentArgs.addSection(section->id); + } + } // The ReportRequest takes ownership of the fd, so we need to dup it. int fd = dup(stream.get()); @@ -339,6 +347,45 @@ Status IncidentService::reportIncidentToDumpstate(unique_fd stream, return Status::ok(); } +Status IncidentService::registerSection(const int id, const String16& name16, + const sp<IIncidentDumpCallback>& callback) { + const char* name = String8(name16).c_str(); + ALOGI("Register section %d: %s", id, name); + if (callback == nullptr) { + return Status::fromExceptionCode(Status::EX_NULL_POINTER); + } + const uid_t callingUid = IPCThreadState::self()->getCallingUid(); + for (int i = 0; i < mRegisteredSections.size(); i++) { + if (mRegisteredSections.at(i)->id == id) { + if (mRegisteredSections.at(i)->uid != callingUid) { + ALOGW("Error registering section %d: calling uid does not match", id); + return Status::fromExceptionCode(Status::EX_SECURITY); + } + mRegisteredSections.at(i) = new BringYourOwnSection(id, name, callingUid, callback); + return Status::ok(); + } + } + mRegisteredSections.push_back(new BringYourOwnSection(id, name, callingUid, callback)); + return Status::ok(); +} + +Status IncidentService::unregisterSection(const int id) { + ALOGI("Unregister section %d", id); + uid_t callingUid = IPCThreadState::self()->getCallingUid(); + for (auto it = mRegisteredSections.begin(); it != mRegisteredSections.end(); it++) { + if ((*it)->id == id) { + if ((*it)->uid != callingUid) { + ALOGW("Error unregistering section %d: calling uid does not match", id); + return Status::fromExceptionCode(Status::EX_SECURITY); + } + mRegisteredSections.erase(it); + return Status::ok(); + } + } + ALOGW("Section %d not found", id); + return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE); +} + Status IncidentService::systemRunning() { if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) { return Status::fromExceptionCode(Status::EX_SECURITY, diff --git a/cmds/incidentd/src/IncidentService.h b/cmds/incidentd/src/IncidentService.h index b2c7f233e11b..49fc566d71af 100644 --- a/cmds/incidentd/src/IncidentService.h +++ b/cmds/incidentd/src/IncidentService.h @@ -40,12 +40,16 @@ using namespace android::base; using namespace android::binder; using namespace android::os; +class BringYourOwnSection; + // ================================================================================ class ReportHandler : public MessageHandler { public: ReportHandler(const sp<WorkDirectory>& workDirectory, - const sp<Broadcaster>& broadcaster, const sp<Looper>& handlerLooper, - const sp<Throttler>& throttler); + const sp<Broadcaster>& broadcaster, + const sp<Looper>& handlerLooper, + const sp<Throttler>& throttler, + const vector<BringYourOwnSection*>& registeredSections); virtual ~ReportHandler(); virtual void handleMessage(const Message& message); @@ -79,6 +83,8 @@ private: nsecs_t mBacklogDelay; sp<Throttler> mThrottler; + const vector<BringYourOwnSection*>& mRegisteredSections; + sp<ReportBatch> mBatch; /** @@ -126,6 +132,11 @@ public: virtual Status reportIncidentToDumpstate(unique_fd stream, const sp<IIncidentReportStatusListener>& listener); + virtual Status registerSection(int id, const String16& name, + const sp<IIncidentDumpCallback>& callback); + + virtual Status unregisterSection(int id); + virtual Status systemRunning(); virtual Status getIncidentReportList(const String16& pkg, const String16& cls, @@ -149,6 +160,7 @@ private: sp<Broadcaster> mBroadcaster; sp<ReportHandler> mHandler; sp<Throttler> mThrottler; + vector<BringYourOwnSection*> mRegisteredSections; /** * Commands print out help. diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp index 02b6bbe6c9b1..aa40f85fd340 100644 --- a/cmds/incidentd/src/Reporter.cpp +++ b/cmds/incidentd/src/Reporter.cpp @@ -364,7 +364,6 @@ void ReportWriter::startSection(int sectionId) { mSectionBufferSuccess = false; mHadError = false; mSectionErrors.clear(); - } void ReportWriter::setSectionStats(const FdBuffer& buffer) { @@ -470,10 +469,13 @@ status_t ReportWriter::writeSection(const FdBuffer& buffer) { // ================================================================================ -Reporter::Reporter(const sp<WorkDirectory>& workDirectory, const sp<ReportBatch>& batch) +Reporter::Reporter(const sp<WorkDirectory>& workDirectory, + const sp<ReportBatch>& batch, + const vector<BringYourOwnSection*>& registeredSections) :mWorkDirectory(workDirectory), mWriter(batch), - mBatch(batch) { + mBatch(batch), + mRegisteredSections(registeredSections) { } Reporter::~Reporter() { @@ -580,50 +582,15 @@ void Reporter::runReport(size_t* reportByteSize) { // For each of the report fields, see if we need it, and if so, execute the command // and report to those that care that we're doing it. for (const Section** section = SECTION_LIST; *section; section++) { - const int sectionId = (*section)->id; - - // If nobody wants this section, skip it. - if (!mBatch->containsSection(sectionId)) { - continue; + if (execute_section(*section, &metadata, reportByteSize) != NO_ERROR) { + goto DONE; } + } - ALOGD("Start incident report section %d '%s'", sectionId, (*section)->name.string()); - IncidentMetadata::SectionStats* sectionMetadata = metadata.add_sections(); - - // Notify listener of starting - mBatch->forEachListener(sectionId, [sectionId](const auto& listener) { - listener->onReportSectionStatus( - sectionId, IIncidentReportStatusListener::STATUS_STARTING); - }); - - // Go get the data and write it into the file descriptors. - mWriter.startSection(sectionId); - err = (*section)->Execute(&mWriter); - mWriter.endSection(sectionMetadata); - - // Sections returning errors are fatal. Most errors should not be fatal. - if (err != NO_ERROR) { - mWriter.error((*section), err, "Section failed. Stopping report."); + for (const Section* section : mRegisteredSections) { + if (execute_section(section, &metadata, reportByteSize) != NO_ERROR) { goto DONE; } - - // The returned max data size is used for throttling too many incident reports. - (*reportByteSize) += sectionMetadata->report_size_bytes(); - - // For any requests that failed during this section, remove them now. We do this - // before calling back about section finished, so listeners do not erroniously get the - // impression that the section succeeded. But we do it here instead of inside - // writeSection so that the callback is done from a known context and not from the - // bowels of a section, where changing the batch could cause odd errors. - cancel_and_remove_failed_requests(); - - // Notify listener of finishing - mBatch->forEachListener(sectionId, [sectionId](const auto& listener) { - listener->onReportSectionStatus( - sectionId, IIncidentReportStatusListener::STATUS_FINISHED); - }); - - ALOGD("Finish incident report section %d '%s'", sectionId, (*section)->name.string()); } DONE: @@ -681,6 +648,55 @@ DONE: ALOGI("Done taking incident report err=%s", strerror(-err)); } +status_t Reporter::execute_section(const Section* section, IncidentMetadata* metadata, + size_t* reportByteSize) { + const int sectionId = section->id; + + // If nobody wants this section, skip it. + if (!mBatch->containsSection(sectionId)) { + return NO_ERROR; + } + + ALOGD("Start incident report section %d '%s'", sectionId, section->name.string()); + IncidentMetadata::SectionStats* sectionMetadata = metadata->add_sections(); + + // Notify listener of starting + mBatch->forEachListener(sectionId, [sectionId](const auto& listener) { + listener->onReportSectionStatus( + sectionId, IIncidentReportStatusListener::STATUS_STARTING); + }); + + // Go get the data and write it into the file descriptors. + mWriter.startSection(sectionId); + status_t err = section->Execute(&mWriter); + mWriter.endSection(sectionMetadata); + + // Sections returning errors are fatal. Most errors should not be fatal. + if (err != NO_ERROR) { + mWriter.error(section, err, "Section failed. Stopping report."); + return err; + } + + // The returned max data size is used for throttling too many incident reports. + (*reportByteSize) += sectionMetadata->report_size_bytes(); + + // For any requests that failed during this section, remove them now. We do this + // before calling back about section finished, so listeners do not erroniously get the + // impression that the section succeeded. But we do it here instead of inside + // writeSection so that the callback is done from a known context and not from the + // bowels of a section, where changing the batch could cause odd errors. + cancel_and_remove_failed_requests(); + + // Notify listener of finishing + mBatch->forEachListener(sectionId, [sectionId](const auto& listener) { + listener->onReportSectionStatus( + sectionId, IIncidentReportStatusListener::STATUS_FINISHED); + }); + + ALOGD("Finish incident report section %d '%s'", sectionId, section->name.string()); + return NO_ERROR; +} + void Reporter::cancel_and_remove_failed_requests() { // Handle a failure in the persisted file if (mPersistedFile != nullptr) { diff --git a/cmds/incidentd/src/Reporter.h b/cmds/incidentd/src/Reporter.h index fb3961ab8b43..cbc8b136e7e3 100644 --- a/cmds/incidentd/src/Reporter.h +++ b/cmds/incidentd/src/Reporter.h @@ -21,6 +21,7 @@ #include "frameworks/base/core/proto/android/os/metadata.pb.h" #include <android/content/ComponentName.h> #include <android/os/IIncidentReportStatusListener.h> +#include <android/os/IIncidentDumpCallback.h> #include <android/os/IncidentReportArgs.h> #include <android/util/protobuf.h> @@ -39,6 +40,7 @@ using namespace std; using namespace android::content; using namespace android::os; +class BringYourOwnSection; class Section; // ================================================================================ @@ -122,7 +124,7 @@ public: void forEachStreamingRequest(const function<void (const sp<ReportRequest>&)>& func); /** - * Call func(request) for each file descriptor that has + * Call func(request) for each file descriptor. */ void forEachFd(int sectionId, const function<void (const sp<ReportRequest>&)>& func); @@ -251,7 +253,9 @@ private: // ================================================================================ class Reporter : public virtual RefBase { public: - Reporter(const sp<WorkDirectory>& workDirectory, const sp<ReportBatch>& batch); + Reporter(const sp<WorkDirectory>& workDirectory, + const sp<ReportBatch>& batch, + const vector<BringYourOwnSection*>& registeredSections); virtual ~Reporter(); @@ -263,6 +267,10 @@ private: ReportWriter mWriter; sp<ReportBatch> mBatch; sp<ReportFile> mPersistedFile; + const vector<BringYourOwnSection*>& mRegisteredSections; + + status_t execute_section(const Section* section, IncidentMetadata* metadata, + size_t* reportByteSize); void cancel_and_remove_failed_requests(); }; diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp index c9277a57bd07..2229e1c7ca07 100644 --- a/cmds/incidentd/src/Section.cpp +++ b/cmds/incidentd/src/Section.cpp @@ -267,7 +267,7 @@ static void* worker_thread_func(void* cookie) { signal(SIGPIPE, sigpipe_handler); WorkerThreadData* data = (WorkerThreadData*)cookie; - status_t err = data->section->BlockingCall(data->pipe.writeFd().get()); + status_t err = data->section->BlockingCall(data->pipe.writeFd()); { unique_lock<mutex> lock(data->lock); @@ -458,7 +458,7 @@ DumpsysSection::DumpsysSection(int id, const char* service, ...) DumpsysSection::~DumpsysSection() {} -status_t DumpsysSection::BlockingCall(int pipeWriteFd) const { +status_t DumpsysSection::BlockingCall(unique_fd& pipeWriteFd) const { // checkService won't wait for the service to show up like getService will. sp<IBinder> service = defaultServiceManager()->checkService(mService); @@ -467,7 +467,7 @@ status_t DumpsysSection::BlockingCall(int pipeWriteFd) const { return NAME_NOT_FOUND; } - service->dump(pipeWriteFd, mArgs); + service->dump(pipeWriteFd.get(), mArgs); return NO_ERROR; } @@ -526,7 +526,7 @@ static inline int32_t get4LE(uint8_t const* src) { return src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24); } -status_t LogSection::BlockingCall(int pipeWriteFd) const { +status_t LogSection::BlockingCall(unique_fd& pipeWriteFd) const { // Open log buffer and getting logs since last retrieved time if any. unique_ptr<logger_list, void (*)(logger_list*)> loggers( gLastLogsRetrieved.find(mLogID) == gLastLogsRetrieved.end() @@ -643,7 +643,7 @@ status_t LogSection::BlockingCall(int pipeWriteFd) const { } } gLastLogsRetrieved[mLogID] = lastTimestamp; - if (!proto.flush(pipeWriteFd) && errno == EPIPE) { + if (!proto.flush(pipeWriteFd.get()) && errno == EPIPE) { ALOGE("[%s] wrote to a broken pipe\n", this->name.string()); return EPIPE; } @@ -660,7 +660,7 @@ TombstoneSection::TombstoneSection(int id, const char* type, const int64_t timeo TombstoneSection::~TombstoneSection() {} -status_t TombstoneSection::BlockingCall(int pipeWriteFd) const { +status_t TombstoneSection::BlockingCall(unique_fd& pipeWriteFd) const { std::unique_ptr<DIR, decltype(&closedir)> proc(opendir("/proc"), closedir); if (proc.get() == nullptr) { ALOGE("opendir /proc failed: %s\n", strerror(errno)); @@ -768,7 +768,7 @@ status_t TombstoneSection::BlockingCall(int pipeWriteFd) const { dumpPipe.readFd().reset(); } - if (!proto.flush(pipeWriteFd) && errno == EPIPE) { + if (!proto.flush(pipeWriteFd.get()) && errno == EPIPE) { ALOGE("[%s] wrote to a broken pipe\n", this->name.string()); if (err != NO_ERROR) { return EPIPE; @@ -778,6 +778,22 @@ status_t TombstoneSection::BlockingCall(int pipeWriteFd) const { return err; } +// ================================================================================ +BringYourOwnSection::BringYourOwnSection(int id, const char* customName, const uid_t callingUid, + const sp<IIncidentDumpCallback>& callback) + : WorkerThreadSection(id, REMOTE_CALL_TIMEOUT_MS), uid(callingUid), mCallback(callback) { + name = "registered "; + name += customName; +} + +BringYourOwnSection::~BringYourOwnSection() {} + +status_t BringYourOwnSection::BlockingCall(unique_fd& pipeWriteFd) const { + android::os::ParcelFileDescriptor pfd(std::move(pipeWriteFd)); + mCallback->onDumpSection(pfd); + return NO_ERROR; +} + } // namespace incidentd } // namespace os } // namespace android diff --git a/cmds/incidentd/src/Section.h b/cmds/incidentd/src/Section.h index fcf12f7336fd..0bb9da9aac5b 100644 --- a/cmds/incidentd/src/Section.h +++ b/cmds/incidentd/src/Section.h @@ -23,6 +23,8 @@ #include <stdarg.h> #include <map> +#include <android/os/IIncidentDumpCallback.h> + #include <utils/String16.h> #include <utils/String8.h> #include <utils/Vector.h> @@ -89,7 +91,7 @@ public: virtual status_t Execute(ReportWriter* writer) const; - virtual status_t BlockingCall(int pipeWriteFd) const = 0; + virtual status_t BlockingCall(unique_fd& pipeWriteFd) const = 0; }; /** @@ -117,7 +119,7 @@ public: DumpsysSection(int id, const char* service, ...); virtual ~DumpsysSection(); - virtual status_t BlockingCall(int pipeWriteFd) const; + virtual status_t BlockingCall(unique_fd& pipeWriteFd) const; private: String16 mService; @@ -132,7 +134,7 @@ public: SystemPropertyDumpsysSection(int id, const char* service, ...); virtual ~SystemPropertyDumpsysSection(); - virtual status_t BlockingCall(int pipeWriteFd) const; + virtual status_t BlockingCall(unique_fd& pipeWriteFd) const; private: String16 mService; @@ -153,7 +155,7 @@ public: LogSection(int id, const char* logID, ...); virtual ~LogSection(); - virtual status_t BlockingCall(int pipeWriteFd) const; + virtual status_t BlockingCall(unique_fd& pipeWriteFd) const; private: log_id_t mLogID; @@ -169,12 +171,29 @@ public: TombstoneSection(int id, const char* type, int64_t timeoutMs = 120000 /* 2 minutes */); virtual ~TombstoneSection(); - virtual status_t BlockingCall(int pipeWriteFd) const; + virtual status_t BlockingCall(unique_fd& pipeWriteFd) const; private: std::string mType; }; +/** + * Section that gets data from a registered dump callback. + */ +class BringYourOwnSection : public WorkerThreadSection { +public: + const uid_t uid; + + BringYourOwnSection(int id, const char* customName, const uid_t callingUid, + const sp<IIncidentDumpCallback>& callback); + virtual ~BringYourOwnSection(); + + virtual status_t BlockingCall(unique_fd& pipeWriteFd) const; + +private: + const sp<IIncidentDumpCallback>& mCallback; +}; + /** * These sections will not be generated when doing an 'all' report, either diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java index 6033655c8513..c2ee6dcd13b2 100644 --- a/cmds/sm/src/com/android/commands/sm/Sm.java +++ b/cmds/sm/src/com/android/commands/sm/Sm.java @@ -103,6 +103,10 @@ public final class Sm { runSetVirtualDisk(); } else if ("set-isolated-storage".equals(op)) { runIsolatedStorage(); + } else if ("start-checkpoint".equals(op)) { + runStartCheckpoint(); + } else if ("supports-checkpoint".equals(op)) { + runSupportsCheckpoint(); } else { throw new IllegalArgumentException(); } @@ -313,6 +317,27 @@ public final class Sm { } } + private void runStartCheckpoint() throws RemoteException { + final String numRetriesString = nextArg(); + if (numRetriesString == null) { + throw new IllegalArgumentException("Expected <num-retries>"); + } + int numRetries; + try { + numRetries = Integer.parseInt(numRetriesString); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("<num-retries> must be a positive integer"); + } + if (numRetries <= 0) { + throw new IllegalArgumentException("<num-retries> must be a positive integer"); + } + mSm.startCheckpoint(numRetries); + } + + private void runSupportsCheckpoint() throws RemoteException { + System.out.println(mSm.supportsCheckpoint()); + } + private String nextArg() { if (mNextArg >= mArgs.length) { return null; @@ -344,6 +369,10 @@ public final class Sm { System.err.println(""); System.err.println(" sm set-isolated-storage [on|off|default]"); System.err.println(""); + System.err.println(" sm start-checkpoint <num-retries>"); + System.err.println(""); + System.err.println(" sm supports-checkpoint"); + System.err.println(""); return 1; } } diff --git a/cmds/statsd/OWNERS b/cmds/statsd/OWNERS index 04464ce02b4f..a61babf32e58 100644 --- a/cmds/statsd/OWNERS +++ b/cmds/statsd/OWNERS @@ -1,7 +1,8 @@ -jianjin@google.com +jeffreyhuang@google.com joeo@google.com jtnguyen@google.com muhammadq@google.com +ruchirr@google.com singhtejinder@google.com tsaichristine@google.com yaochen@google.com diff --git a/cmds/statsd/src/FieldValue.h b/cmds/statsd/src/FieldValue.h index 6fc1e236c661..967fd323e5a0 100644 --- a/cmds/statsd/src/FieldValue.h +++ b/cmds/statsd/src/FieldValue.h @@ -261,6 +261,11 @@ inline Matcher getSimpleMatcher(int32_t tag, size_t field) { return Matcher(Field(tag, getSimpleField(field)), 0xff7f0000); } +inline Matcher getFirstUidMatcher(int32_t atomId) { + int32_t pos[] = {1, 1, 1}; + return Matcher(Field(atomId, pos, 2), 0xff7f7f7f); +} + /** * A wrapper for a union type to contain multiple types of values. * diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index 1ca19c3417c2..ada2f2de28f4 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -1312,6 +1312,13 @@ Status StatsService::unregisterPullAtomCallback(int32_t uid, int32_t atomTag) { return Status::ok(); } +Status StatsService::unregisterNativePullAtomCallback(int32_t atomTag) { + VLOG("StatsService::unregisterNativePullAtomCallback called."); + int32_t uid = IPCThreadState::self()->getCallingUid(); + mPullerManager->UnregisterPullAtomCallback(uid, atomTag); + return Status::ok(); +} + Status StatsService::sendBinaryPushStateChangedAtom(const android::String16& trainNameIn, const int64_t trainVersionCodeIn, const int options, diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h index c9a9072ecb92..7990e5e7d018 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -203,6 +203,11 @@ public: virtual Status unregisterPullAtomCallback(int32_t uid, int32_t atomTag) override; /** + * Binder call to unregister any existing callback for the given atom and calling uid. + */ + virtual Status unregisterNativePullAtomCallback(int32_t atomTag) override; + + /** * Binder call to log BinaryPushStateChanged atom. */ virtual Status sendBinaryPushStateChangedAtom( diff --git a/cmds/statsd/src/atom_field_options.proto b/cmds/statsd/src/atom_field_options.proto index 6d2bd04756ac..946c55087005 100644 --- a/cmds/statsd/src/atom_field_options.proto +++ b/cmds/statsd/src/atom_field_options.proto @@ -30,6 +30,8 @@ enum StateField { PRIMARY = 1; // The field that represents the state. It's an exclusive state. EXCLUSIVE = 2; + + PRIMARY_FIELD_FIRST_UID = 3; } // Used to annotate an atom that reprsents a state change. A state change atom must have exactly ONE diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index fb43783ca9a6..4372e2245ee8 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -53,6 +53,7 @@ import "frameworks/base/core/proto/android/stats/mediametrics/mediametrics.proto import "frameworks/base/core/proto/android/stats/mediaprovider/mediaprovider_enums.proto"; import "frameworks/base/core/proto/android/stats/storage/storage_enums.proto"; import "frameworks/base/core/proto/android/stats/style/style_enums.proto"; +import "frameworks/base/core/proto/android/stats/sysui/notification_enums.proto"; import "frameworks/base/core/proto/android/telecomm/enums.proto"; import "frameworks/base/core/proto/android/telephony/enums.proto"; import "frameworks/base/core/proto/android/view/enums.proto"; @@ -126,10 +127,10 @@ message Atom { AppStartOccurred app_start_occurred = 48; AppStartCanceled app_start_canceled = 49; AppStartFullyDrawn app_start_fully_drawn = 50; - LmkKillOccurred lmk_kill_occurred = 51; + LmkKillOccurred lmk_kill_occurred = 51 [(module) = "lmkd"]; PictureInPictureStateChanged picture_in_picture_state_changed = 52; WifiMulticastLockStateChanged wifi_multicast_lock_state_changed = 53 [(module) = "wifi"]; - LmkStateChanged lmk_state_changed = 54; + LmkStateChanged lmk_state_changed = 54 [(module) = "lmkd"]; AppStartMemoryStateCaptured app_start_memory_state_captured = 55; ShutdownSequenceReported shutdown_sequence_reported = 56; BootSequenceReported boot_sequence_reported = 57; @@ -324,8 +325,6 @@ message Atom { 228 [(allow_from_any_uid) = true]; PerfettoUploaded perfetto_uploaded = 229 [(module) = "perfetto"]; VmsClientConnectionStateChanged vms_client_connection_state_changed = 230; - GpsLocationStatusReported gps_location_status_reported = 231; - GpsTimeToFirstFixReported gps_time_to_first_fix_reported = 232; MediaProviderScanEvent media_provider_scan_event = 233 [(module) = "mediaprovider"]; MediaProviderDeletionEvent media_provider_deletion_event = 234 [(module) = "mediaprovider"]; MediaProviderPermissionEvent media_provider_permission_event = @@ -334,10 +333,18 @@ message Atom { MediaProviderIdleMaintenance media_provider_idle_maintenance = 237 [(module) = "mediaprovider"]; RebootEscrowRecoveryReported reboot_escrow_recovery_reported = 238; + BootTimeEventDuration boot_time_event_duration_reported = 239; + BootTimeEventElapsedTime boot_time_event_elapsed_time_reported = 240; + BootTimeEventUtcTime boot_time_event_utc_time_reported = 241; + BootTimeEventErrorCode boot_time_event_error_code_reported = 242; + UserspaceRebootReported userspace_reboot_reported = 243; + NotificationReported notification_reported = 244; + NotificationPanelReported notification_panel_reported = 245; + NotificationChannelModified notification_panel_modified = 246; } // Pulled events will start at field 10000. - // Next: 10069 + // Next: 10071 oneof pulled { WifiBytesTransfer wifi_bytes_transfer = 10000; WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001; @@ -394,7 +401,7 @@ message Atom { ExternalStorageInfo external_storage_info = 10053; GpuStatsGlobalInfo gpu_stats_global_info = 10054; GpuStatsAppInfo gpu_stats_app_info = 10055; - SystemIonHeapSize system_ion_heap_size = 10056; + SystemIonHeapSize system_ion_heap_size = 10056 [deprecated = true]; AppsOnExternalStorageInfo apps_on_external_storage_info = 10057; FaceSettings face_settings = 10058; CoolingDevice cooling_device = 10059; @@ -407,6 +414,8 @@ message Atom { NotificationRemoteViews notification_remote_views = 10066; DangerousPermissionStateSampled dangerous_permission_state_sampled = 10067; GraphicsStats graphics_stats = 10068; + RuntimeAppOpsAccess runtime_app_ops_access = 10069; + IonHeapSize ion_heap_size = 10070; } // DO NOT USE field numbers above 100,000 in AOSP. @@ -731,27 +740,6 @@ message GpsSignalQualityChanged { optional android.server.location.GpsSignalQualityEnum level = 1; } -/** - * Gps location status report - * - * Logged from: - * /frameworks/base/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java - */ -message GpsLocationStatusReported { - // Boolean stating if location was acquired - optional bool location_success = 1; -} - -/** - * Gps log time to first fix report - * - * Logged from: - * /frameworks/base/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java - */ -message GpsTimeToFirstFixReported { - // int32 reporting the time to first fix in milliseconds - optional int32 time_to_first_fix_millis = 1; -} /** * Logs when a sync manager sync state changes. @@ -908,14 +896,16 @@ message CameraStateChanged { * TODO */ message WakelockStateChanged { - repeated AttributionNode attribution_node = 1; + repeated AttributionNode attribution_node = 1 + [(state_field_option).option = PRIMARY_FIELD_FIRST_UID]; // The type (level) of the wakelock; e.g. a partial wakelock or a full wakelock. // From frameworks/base/core/proto/android/os/enums.proto. - optional android.os.WakeLockLevelEnum type = 2; + optional android.os.WakeLockLevelEnum type = 2 [(state_field_option).option = PRIMARY]; + ; // The wakelock tag (Called tag in the Java API, sometimes name elsewhere). - optional string tag = 3; + optional string tag = 3 [(state_field_option).option = PRIMARY]; enum State { RELEASE = 0; @@ -923,7 +913,7 @@ message WakelockStateChanged { CHANGE_RELEASE = 2; CHANGE_ACQUIRE = 3; } - optional State state = 4; + optional State state = 4 [(state_field_option).option = EXCLUSIVE]; } /** @@ -3301,6 +3291,10 @@ message GenericAtom { * this button" or "this dialog was displayed". * Keep the UI event stream clean: don't use for system or background events. * Log using the UiEventLogger wrapper - don't write with the StatsLog API directly. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/ + * frameworks/base/packages/SystemUI/src/com/android/systemui/ */ message UiEventReported { // The event_id. @@ -3312,6 +3306,122 @@ message UiEventReported { } /** + * Reports a notification was created or updated. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/notification/ + */ +message NotificationReported { + // The event_id (as for UiEventReported). + optional int32 event_id = 1; + // The notifying app's uid and package. + optional int32 uid = 2 [(is_uid) = true]; + optional string package_name = 3; + // A small system-assigned identifier for the notification. + // Locally probably-unique, but expect collisions across users and/or days. + optional int32 instance_id = 4; + // The app-assigned notification ID and tag + optional int32 notification_id = 5; + optional string notification_tag = 6; + optional string channel_id = 7; // App-assigned channel ID + + // Grouping information + optional string group_id = 8; // Group the notification currently belongs to + optional int32 group_instance_id = 9; // Instance_id of the group-summary notification + optional bool is_group_summary = 10; // Tags the group-summary notification + + // Attributes + optional string category = 11; // App-assigned notification category (API-defined strings) + optional int32 style = 12; // App-assigned notification style + optional int32 num_people = 13; // Number of Person records attached to the notification + + // Ordering, importance and interruptiveness + + optional int32 position = 14; // Position in NotificationManager's list + + optional android.stats.sysui.NotificationImportance importance = 15; + optional int32 alerting = 16; // Bitfield, 1=buzz 2=beep 4=blink + + enum NotificationImportanceExplanation { + IMPORTANCE_EXPLANATION_UNKNOWN = 0; + IMPORTANCE_EXPLANATION_APP = 1; // App-specified channel importance. + IMPORTANCE_EXPLANATION_USER = 2; // User-specified channel importance. + IMPORTANCE_EXPLANATION_ASST = 3; // Notification Assistant override. + IMPORTANCE_EXPLANATION_SYSTEM = 4; // System override. + // Like _APP, but based on pre-channels priority signal. + IMPORTANCE_EXPLANATION_APP_PRE_CHANNELS = 5; + } + + optional NotificationImportanceExplanation importance_source = 17; + optional android.stats.sysui.NotificationImportance importance_initial = 18; + optional NotificationImportanceExplanation importance_initial_source = 19; + optional android.stats.sysui.NotificationImportance importance_asst = 20; + optional int32 assistant_hash = 21; + optional float assistant_ranking_score = 22; +} + +message Notification { + // The notifying app's uid and package. + optional int32 uid = 1 [(is_uid) = true]; + optional string package_name = 2; + // A small system-assigned identifier for the notification. + optional int32 instance_id = 3; + + // Grouping information. + optional int32 group_instance_id = 4; + optional bool is_group_summary = 5; + + // The section of the shade that the notification is in. + // See NotificationSectionsManager.PriorityBucket. + enum NotificationSection { + SECTION_UNKNOWN = 0; + SECTION_PEOPLE = 1; + SECTION_ALERTING = 2; + SECTION_SILENT = 3; + } + optional NotificationSection section = 6; +} + +message NotificationList { + repeated Notification notifications = 1; // An ordered sequence of notifications. +} + +/** + * Reports a notification panel was displayed, e.g. from the lockscreen or status bar. + * + * Logged from: + * frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/ + */ +message NotificationPanelReported { + // The event_id (as for UiEventReported). + optional int32 event_id = 1; + optional int32 num_notifications = 2; + // The notifications in the panel, in the order that they appear there. + optional NotificationList notifications = 3 [(log_mode) = MODE_BYTES]; +} + +/** + * Reports a notification channel, or channel group, was created, updated, or deleted. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/notification/ + */ +message NotificationChannelModified { + // The event_id (as for UiEventReported). + optional int32 event_id = 1; + // The notifying app's uid and package. + optional int32 uid = 2 [(is_uid) = true]; + optional string package_name = 3; + // App-assigned notification channel ID or channel-group ID + optional string channel_id = 4; + // Previous importance setting, if applicable + optional android.stats.sysui.NotificationImportance old_importance = 5; + // New importance setting + optional android.stats.sysui.NotificationImportance importance = 6; +} + + +/** * Logs when a biometric acquire event occurs. * * Logged from: @@ -3499,12 +3609,14 @@ message BinaryPushStateChanged { INSTALL_FAILURE_DOWNLOAD = 23; INSTALL_FAILURE_STATE_MISMATCH = 24; INSTALL_FAILURE_COMMIT = 25; + REBOOT_TRIGGERED = 26; } optional State state = 6; // Possible experiment ids for monitoring this push. optional TrainExperimentIds experiment_ids = 7 [(log_mode) = MODE_BYTES]; // user id optional int32 user_id = 8; + optional int32 reason = 9; } /* Test atom, is not logged anywhere */ @@ -3925,6 +4037,207 @@ message MediaProviderIdleMaintenance { optional float normalized_expired_media = 5; } +/** + * Represents boot time event with duration in ms. + * + * Logged from: bootstat and various system server components. Check each enums for details. + */ +message BootTimeEventDuration { + enum DurationEvent { + UNKNOWN = 0; + // Bootloader time excluding BOOTLOADER_UI_WAIT + boot complete time. Logged from bootstat. + ABSOLUTE_BOOT_TIME = 1; + // Bootloader's 1st stage execution time. + // Logged from bootstat. + BOOTLOADER_FIRST_STAGE_EXEC = 2; + // Bootloader's 1st stage loading time. + // Logged from bootstat. + BOOTLOADER_FIRST_STAGE_LOAD = 3; + // Bootloader's kernel loading time. + // Logged from bootstat. + BOOTLOADER_KERNEL_LOAD = 4; + // Bootloader's 2nd stage execution time. + // Logged from bootstat. + BOOTLOADER_SECOND_STAGE_EXEC = 5; + // Bootloader's 2nd stage loading time. + // Logged from bootstat. + BOOTLOADER_SECOND_STAGE_LOAD = 6; + // Duration for Bootloader to show unlocked device's warning UI. This should not happen + // for locked device. + // Logged from bootstat. + BOOTLOADER_UI_WAIT = 7; + // Total time spend in bootloader. This is the sum of all BOOTLOADER_* listed above. + // Logged from bootstat. + BOOTLOADER_TOTAL = 8; + // Shutdown duration inside init for the reboot before the current boot up. + // Logged from f/b/services/.../BootReceiver.java. + SHUTDOWN_DURATION = 9; + // Total time for mounting of disk devices during bootup. + // Logged from f/b/services/.../BootReceiver.java. + MOUNT_DEFAULT_DURATION = 10; + // Total time for early stage mounting of disk devices during bootup. + // Logged from f/b/services/.../BootReceiver.java. + MOUNT_EARLY_DURATION = 11; + // Total time for late stage mounting of disk devices during bootup. + // Logged from f/b/services/.../BootReceiver.java. + MOUNT_LATE_DURATION = 12; + // Average time to scan non-system app after OTA + // Logged from f/b/services/.../PackageManagerService.java + OTA_PACKAGE_MANAGER_INIT_TIME = 13; + // Time to initialize Package manager after OTA + // Logged from f/b/services/.../PackageManagerService.java + OTA_PACKAGE_MANAGER_DATA_APP_AVG_SCAN_TIME = 14; + // Time to scan all system app from Package manager after OTA + // Logged from f/b/services/.../PackageManagerService.java + OTA_PACKAGE_MANAGER_SYSTEM_APP_AVG_SCAN_TIME = 15; + // Init's total time for cold boot stage. + // Logged from bootstat. + COLDBOOT_WAIT = 16; + // Init's total time for initializing selinux. + // Logged from bootstat. + SELINUX_INIT = 17; + // Time since last factory reset. + // Logged from bootstat. + FACTORY_RESET_TIME_SINCE_RESET = 18; + } + + // Type of the event. + optional DurationEvent event = 1; + // Duration of the event in ms. + optional int64 duration_millis = 2; +} + +/** + * Represents the start of specific boot time event during bootup in ms. This is usually a time + * since boot-up. + * + * Logged from: bootstat and various system server components. Check each enums for details. + */ +message BootTimeEventElapsedTime { + enum ElapsedTimeEvent { + UNKNOWN = 0; + // Time when init starts 1st stage. Logged from bootstat. + ANDROID_INIT_STAGE_1 = 1; + // Time when sys.boot_completed prop is set. + // Logged from bootstat. + BOOT_COMPLETE = 2; + // BOOT_COMPLETE for encrypted device. + BOOT_COMPLETE_ENCRYPTION = 3; + // BOOT_COMPLETE for device with no encryption. + BOOT_COMPLETE_NO_ENCRYPTION = 4; + // Adjusted BOOT_COMPLETE for encrypted device extracting decryption time. + BOOT_COMPLETE_POST_DESCRYPT = 5; + // BOOT_COMPLETE after factory reset. + FACTORY_RESET_BOOT_COMPLETE = 6; + // BOOT_COMPLETE_NO_ENCRYPTION after factory reset. + FACTORY_RESET_BOOT_COMPLETE_NO_ENCRYPTION = 7; + // BOOT_COMPLETE_POST_DESCRYPT after factory reset. + FACTORY_RESET_BOOT_COMPLETE_POST_DESCRYPT = 8; + // BOOT_COMPLETE after OTA. + OTA_BOOT_COMPLETE = 9; + // BOOT_COMPLETE_NO_ENCRYPTION after OTA. + OTA_BOOT_COMPLETE_NO_ENCRYPTION = 10; + // BOOT_COMPLETE_POST_DESCRYPT after OTA. + OTA_BOOT_COMPLETE_POST_DESCRYPT = 11; + // Time when the system starts sending LOCKED_BOOT_COMPLETED broadcast. + // Logged from f/b/services/.../UserController.java + FRAMEWORK_LOCKED_BOOT_COMPLETED = 12; + // Time when the system starts sending BOOT_COMPLETED broadcast. + // Logged from f/b/services/.../UserController.java + FRAMEWORK_BOOT_COMPLETED = 13; + // Time when the package manager starts init. + // Logged from f/b/services/.../SystemServer.java + PACKAGE_MANAGER_INIT_START = 14; + // Time when package manager is ready + // Logged from f/b/services/.../SystemServer.java + PACKAGE_MANAGER_INIT_READY = 15; + // Represents the time when user has entered unlock credential for system with user pin. + // Logged from bootstat. + POST_DECRYPT = 16; + // Represents the start of zygote's init. + // Logged from zygote itself. + ZYGOTE_INIT_START = 17; + // Represents the start of secondary zygote's init. + // TODO: add logging to zygote + SECONDARY_ZYGOTE_INIT_START = 18; + // Represents the start of system server's init. + // Logged from f/b/services/.../SystemServer.java + SYSTEM_SERVER_INIT_START = 19; + // Represents the completion of system server's init. + // Logged from f/b/services/.../SystemServer.java + SYSTEM_SERVER_READY = 20; + // Represents the start of launcher during boot-up. + // TODO: add logging + LAUNCHER_START = 21; + // Represents the completion of launcher's initial rendering. User can use other apps from + // launcher from this point. + // TODO: add logging + LAUNCHER_SHOWN = 22; + } + + // Type of the event. + optional ElapsedTimeEvent event = 1; + // Time since bootup for the event. + // It should be acquired from SystemClock elapsedRealtime() call or equivalent. + optional int64 time_millis = 2; +} + +/** + * Boot time events with UTC time. + * + * Logged from: bootstat and various system server components. Check each enums for details. + */ +message BootTimeEventUtcTime { + enum UtcTimeEvent { + UNKNOWN = 0; + // Time of the bootstat's marking of 1st boot after the last factory reset. + // Logged from bootstat. + FACTORY_RESET_RESET_TIME = 1; + // The time when bootstat records FACTORY_RESET_* events. This is close to + // BOOT_COMPLETE time for the current bootup. + // Logged from bootstat. + FACTORY_RESET_CURRENT_TIME = 2; + // DUplicate of FACTORY_RESET_RESET_TIME added for debugging purpose. + // Logged from bootstat. + FACTORY_RESET_RECORD_VALUE = 3; + } + + // Type of the event. + optional UtcTimeEvent event = 1; + // UTC time for the event. + optional int64 utc_time_secs = 2; +} + +/** + * Boot time events representing specific error code during bootup. + * Meaning of error code can be different per each event type. + * + * Logged from: bootstat and various system server components. Check each enums for details. + */ +message BootTimeEventErrorCode { + enum ErrorCodeEvent { + UNKNOWN = 0; + // Linux error code for time() call to get the current UTC time. + // Logged from bootstat. + FACTORY_RESET_CURRENT_TIME_FAILURE = 1; + // Represents UmountStat before the reboot for the current boot up. Error codes defined + // as UMOUNT_STAT_* from init/reboot.cpp. + // Logged from f/b/services/.../BootReceiver.java. + SHUTDOWN_UMOUNT_STAT = 2; + // Reprepsents fie system mounting error code of /data partition for the current boot. + // Error codes defined as combination of FsStatFlags from system/core/fs_mgr/fs_mgr.cpp. + // Logged from f/b/services/.../BootReceiver.java. + FS_MGR_FS_STAT_DATA_PARTITION = 3; + } + + // Type of the event. + optional ErrorCodeEvent event = 1; + // error code defined per each event type. + // For example, this can have a value of FsStatFlags.FS_STAT_FULL_MOUNT_FAILED for the event of + // FS_MGR_FS_STAT. + optional int32 error_code = 2; +} + ////////////////////////////////////////////////////////////////////// // Pulled atoms below this line // ////////////////////////////////////////////////////////////////////// @@ -6595,6 +6908,7 @@ message TrainInfo { INSTALL_FAILURE_DOWNLOAD = 23; INSTALL_FAILURE_STATE_MISMATCH = 24; INSTALL_FAILURE_COMMIT = 25; + REBOOT_TRIGGERED = 26; } optional Status status = 4; } @@ -6737,11 +7051,27 @@ message GpuStatsAppInfo { * Pulled from StatsCompanionService. */ message SystemIonHeapSize { + // Deprecated due to limited support of ion stats in debugfs. + // Use `IonHeapSize` instead. + option deprecated = true; + // Size of the system ion heap in bytes. + // Read from debugfs. optional int64 size_in_bytes = 1; } /* + * Logs the total size of the ion heap. + * + * Pulled from StatsCompanionService. + */ +message IonHeapSize { + // Total size of all ion heaps in kilobytes. + // Read from: /sys/kernel/ion/total_heaps_kb. + optional int32 total_size_kb = 1; +} + +/* * Logs the per-process size of the system ion heap. * * Pulled from StatsCompanionService. @@ -6879,7 +7209,7 @@ message AppOps { // Uid of the package requesting the op optional int32 uid = 1 [(is_uid) = true]; - // Nmae of the package performing the op + // Name of the package performing the op optional string package_name = 2; // operation id; maps to the OP_* constants in AppOpsManager.java @@ -7665,3 +7995,73 @@ message GraphicsStats { // more apps are running / rendering. optional bool is_today = 16; } + +/** + * Message related to dangerous (runtime) app ops access + */ +message RuntimeAppOpsAccess { + // Uid of the package accessing app op + optional int32 uid = 1 [(is_uid) = true]; + + // Name of the package accessing app op + optional string package_name = 2; + + // operation id; maps to the OP_* constants in AppOpsManager.java + optional int32 op_id = 3; + + // feature id; provided by developer when accessing related API, limited at 50 chars by API. + // Features must be provided through manifest using <feature> tag available in R and above. + optional string feature_id = 4; + + // message related to app op access, limited to 600 chars by API + optional string message = 5; + + enum SamplingStrategy { + DEFAULT = 0; + UNIFORM = 1; + RARELY_USED = 2; + } + + // sampling strategy used to collect this message + optional SamplingStrategy sampling_strategy = 6; +} + +/* + * Logs userspace reboot outcome and duration. + * + * Logged from: + * frameworks/base/core/java/com/android/server/BootReceiver.java + */ +message UserspaceRebootReported { + // Possible outcomes of userspace reboot. + enum Outcome { + // Default value in case platform failed to determine the outcome. + OUTCOME_UNKNOWN = 0; + // Userspace reboot succeeded (i.e. boot completed without a fall back to hard reboot). + SUCCESS = 1; + // Userspace reboot shutdown sequence was aborted. + FAILED_SHUTDOWN_SEQUENCE_ABORTED = 2; + // Remounting userdata into checkpointing mode failed. + FAILED_USERDATA_REMOUNT = 3; + // Device didn't finish booting before timeout and userspace reboot watchdog issued a hard + // reboot. + FAILED_USERSPACE_REBOOT_WATCHDOG_TRIGGERED = 4; + } + // Outcome of userspace reboot. Always set. + optional Outcome outcome = 1; + // Duration of userspace reboot in case it has a successful outcome. + // Duration is measured as time between userspace reboot was initiated and until boot completed + // (e.g. sys.boot_completed=1). + optional int64 duration_millis = 2; + // State of primary user's (user0) credential encryption storage. + enum UserEncryptionState { + // Default value. + USER_ENCRYPTION_STATE_UNKNOWN = 0; + // Credential encrypted storage is unlocked. + UNLOCKED = 1; + // Credential encrypted storage is locked. + LOCKED = 2; + } + // State of primary user's encryption storage at the moment boot completed. Always set. + optional UserEncryptionState user_encryption_state = 3; +} diff --git a/cmds/statsd/src/external/StatsCallbackPuller.cpp b/cmds/statsd/src/external/StatsCallbackPuller.cpp index 0e6b677abb46..e5a83a27bfcf 100644 --- a/cmds/statsd/src/external/StatsCallbackPuller.cpp +++ b/cmds/statsd/src/external/StatsCallbackPuller.cpp @@ -42,7 +42,7 @@ StatsCallbackPuller::StatsCallbackPuller(int tagId, const sp<IPullAtomCallback>& } bool StatsCallbackPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) { - VLOG("StatsCallbackPuller called for tag %d", mTagId) + VLOG("StatsCallbackPuller called for tag %d", mTagId); if(mCallback == nullptr) { ALOGW("No callback registered"); return false; diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp index b5680331f63e..d5cda85f412a 100644 --- a/cmds/statsd/src/external/StatsPullerManager.cpp +++ b/cmds/statsd/src/external/StatsPullerManager.cpp @@ -60,31 +60,6 @@ const int64_t NO_ALARM_UPDATE = INT64_MAX; std::map<PullerKey, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = { - // wifi_bytes_transfer_by_fg_bg - {{.atomTag = android::util::WIFI_BYTES_TRANSFER_BY_FG_BG}, - {.additiveFields = {3, 4, 5, 6}, - .puller = new StatsCompanionServicePuller(android::util::WIFI_BYTES_TRANSFER_BY_FG_BG)}}, - - // mobile_bytes_transfer - {{.atomTag = android::util::MOBILE_BYTES_TRANSFER}, - {.additiveFields = {2, 3, 4, 5}, - .puller = new StatsCompanionServicePuller(android::util::MOBILE_BYTES_TRANSFER)}}, - - // mobile_bytes_transfer_by_fg_bg - {{.atomTag = android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG}, - {.additiveFields = {3, 4, 5, 6}, - .puller = - new StatsCompanionServicePuller(android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG)}}, - - // bluetooth_bytes_transfer - {{.atomTag = android::util::BLUETOOTH_BYTES_TRANSFER}, - {.additiveFields = {2, 3}, - .puller = new StatsCompanionServicePuller(android::util::BLUETOOTH_BYTES_TRANSFER)}}, - - // kernel_wakelock - {{.atomTag = android::util::KERNEL_WAKELOCK}, - {.puller = new StatsCompanionServicePuller(android::util::KERNEL_WAKELOCK)}}, - // subsystem_sleep_state {{.atomTag = android::util::SUBSYSTEM_SLEEP_STATE}, {.puller = new SubsystemSleepStatePuller()}}, @@ -93,49 +68,6 @@ std::map<PullerKey, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = { {{.atomTag = android::util::ON_DEVICE_POWER_MEASUREMENT}, {.puller = new PowerStatsPuller()}}, - // cpu_time_per_freq - {{.atomTag = android::util::CPU_TIME_PER_FREQ}, - {.additiveFields = {3}, - .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_FREQ)}}, - - // cpu_time_per_uid - {{.atomTag = android::util::CPU_TIME_PER_UID}, - {.additiveFields = {2, 3}, - .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_UID)}}, - - // cpu_time_per_uid_freq - // the throttling is 3sec, handled in - // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader - {{.atomTag = android::util::CPU_TIME_PER_UID_FREQ}, - {.additiveFields = {4}, - .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_UID_FREQ)}}, - - // cpu_active_time - // the throttling is 3sec, handled in - // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader - {{.atomTag = android::util::CPU_ACTIVE_TIME}, - {.additiveFields = {2}, - .puller = new StatsCompanionServicePuller(android::util::CPU_ACTIVE_TIME)}}, - - // cpu_cluster_time - // the throttling is 3sec, handled in - // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader - {{.atomTag = android::util::CPU_CLUSTER_TIME}, - {.additiveFields = {3}, - .puller = new StatsCompanionServicePuller(android::util::CPU_CLUSTER_TIME)}}, - - // wifi_activity_energy_info - {{.atomTag = android::util::WIFI_ACTIVITY_INFO}, - {.puller = new StatsCompanionServicePuller(android::util::WIFI_ACTIVITY_INFO)}}, - - // modem_activity_info - {{.atomTag = android::util::MODEM_ACTIVITY_INFO}, - {.puller = new StatsCompanionServicePuller(android::util::MODEM_ACTIVITY_INFO)}}, - - // bluetooth_activity_info - {{.atomTag = android::util::BLUETOOTH_ACTIVITY_INFO}, - {.puller = new StatsCompanionServicePuller(android::util::BLUETOOTH_ACTIVITY_INFO)}}, - // system_elapsed_realtime {{.atomTag = android::util::SYSTEM_ELAPSED_REALTIME}, {.coolDownNs = NS_PER_SEC, @@ -143,10 +75,6 @@ std::map<PullerKey, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = { .pullTimeoutNs = NS_PER_SEC / 2, }}, - // system_uptime - {{.atomTag = android::util::SYSTEM_UPTIME}, - {.puller = new StatsCompanionServicePuller(android::util::SYSTEM_UPTIME)}}, - // remaining_battery_capacity {{.atomTag = android::util::REMAINING_BATTERY_CAPACITY}, {.puller = new ResourceHealthManagerPuller(android::util::REMAINING_BATTERY_CAPACITY)}}, @@ -167,45 +95,6 @@ std::map<PullerKey, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = { {{.atomTag = android::util::BATTERY_CYCLE_COUNT}, {.puller = new ResourceHealthManagerPuller(android::util::BATTERY_CYCLE_COUNT)}}, - // process_memory_state - {{.atomTag = android::util::PROCESS_MEMORY_STATE}, - {.additiveFields = {4, 5, 6, 7, 8}, - .puller = new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_STATE)}}, - - // process_memory_high_water_mark - {{.atomTag = android::util::PROCESS_MEMORY_HIGH_WATER_MARK}, - {.puller = - new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_HIGH_WATER_MARK)}}, - - // process_memory_snapshot - {{.atomTag = android::util::PROCESS_MEMORY_SNAPSHOT}, - {.puller = new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_SNAPSHOT)}}, - - // system_ion_heap_size - {{.atomTag = android::util::SYSTEM_ION_HEAP_SIZE}, - {.puller = new StatsCompanionServicePuller(android::util::SYSTEM_ION_HEAP_SIZE)}}, - - // process_system_ion_heap_size - {{.atomTag = android::util::PROCESS_SYSTEM_ION_HEAP_SIZE}, - {.puller = new StatsCompanionServicePuller(android::util::PROCESS_SYSTEM_ION_HEAP_SIZE)}}, - - // temperature - {{.atomTag = android::util::TEMPERATURE}, - {.puller = new StatsCompanionServicePuller(android::util::TEMPERATURE)}}, - - // cooling_device - {{.atomTag = android::util::COOLING_DEVICE}, - {.puller = new StatsCompanionServicePuller(android::util::COOLING_DEVICE)}}, - - // binder_calls - {{.atomTag = android::util::BINDER_CALLS}, - {.additiveFields = {4, 5, 6, 8, 12}, - .puller = new StatsCompanionServicePuller(android::util::BINDER_CALLS)}}, - - // binder_calls_exceptions - {{.atomTag = android::util::BINDER_CALLS_EXCEPTIONS}, - {.puller = new StatsCompanionServicePuller(android::util::BINDER_CALLS_EXCEPTIONS)}}, - // looper_stats {{.atomTag = android::util::LOOPER_STATS}, {.additiveFields = {5, 6, 7, 8, 9}, @@ -249,10 +138,6 @@ std::map<PullerKey, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = { .coolDownNs = 3 * NS_PER_SEC, .puller = new StatsCompanionServicePuller(android::util::DISK_IO)}}, - // PowerProfile constants for power model calculations. - {{.atomTag = android::util::POWER_PROFILE}, - {.puller = new StatsCompanionServicePuller(android::util::POWER_PROFILE)}}, - // Process cpu stats. Min cool-down is 5 sec, inline with what AcitivityManagerService uses. {{.atomTag = android::util::PROCESS_CPU_TIME}, {.coolDownNs = 5 * NS_PER_SEC /* min cool-down in seconds*/, @@ -261,20 +146,6 @@ std::map<PullerKey, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = { {.additiveFields = {7, 9, 11, 13, 15, 17, 19, 21}, .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_THREAD_FREQ)}}, - // DeviceCalculatedPowerUse. - {{.atomTag = android::util::DEVICE_CALCULATED_POWER_USE}, - {.puller = new StatsCompanionServicePuller(android::util::DEVICE_CALCULATED_POWER_USE)}}, - - // DeviceCalculatedPowerBlameUid. - {{.atomTag = android::util::DEVICE_CALCULATED_POWER_BLAME_UID}, - {.puller = new StatsCompanionServicePuller( - android::util::DEVICE_CALCULATED_POWER_BLAME_UID)}}, - - // DeviceCalculatedPowerBlameOther. - {{.atomTag = android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER}, - {.puller = new StatsCompanionServicePuller( - android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER)}}, - // DebugElapsedClock. {{.atomTag = android::util::DEBUG_ELAPSED_CLOCK}, {.additiveFields = {1, 2, 3, 4}, @@ -285,10 +156,6 @@ std::map<PullerKey, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = { {.additiveFields = {1, 2, 3, 4}, .puller = new StatsCompanionServicePuller(android::util::DEBUG_FAILING_ELAPSED_CLOCK)}}, - // BuildInformation. - {{.atomTag = android::util::BUILD_INFORMATION}, - {.puller = new StatsCompanionServicePuller(android::util::BUILD_INFORMATION)}}, - // RoleHolder. {{.atomTag = android::util::ROLE_HOLDER}, {.puller = new StatsCompanionServicePuller(android::util::ROLE_HOLDER)}}, diff --git a/cmds/statsd/src/state/StateTracker.cpp b/cmds/statsd/src/state/StateTracker.cpp index ef59c9242cb2..3ad21e0c96ae 100644 --- a/cmds/statsd/src/state/StateTracker.cpp +++ b/cmds/statsd/src/state/StateTracker.cpp @@ -28,10 +28,14 @@ namespace statsd { StateTracker::StateTracker(const int32_t atomId, const util::StateAtomFieldOptions& stateAtomInfo) : mAtomId(atomId), mStateField(getSimpleMatcher(atomId, stateAtomInfo.exclusiveField)) { // create matcher for each primary field - // TODO(tsaichristine): b/142108433 handle when primary field is first uid in chain - for (const auto& primary : stateAtomInfo.primaryFields) { - Matcher matcher = getSimpleMatcher(atomId, primary); - mPrimaryFields.push_back(matcher); + for (const auto& primaryField : stateAtomInfo.primaryFields) { + if (primaryField == util::FIRST_UID_IN_CHAIN) { + Matcher matcher = getFirstUidMatcher(atomId); + mPrimaryFields.push_back(matcher); + } else { + Matcher matcher = getSimpleMatcher(atomId, primaryField); + mPrimaryFields.push_back(matcher); + } } // TODO(tsaichristine): b/142108433 set default state, reset state, and nesting diff --git a/cmds/statsd/src/state/StateTracker.h b/cmds/statsd/src/state/StateTracker.h index 7453370f25fd..70f16274c7f6 100644 --- a/cmds/statsd/src/state/StateTracker.h +++ b/cmds/statsd/src/state/StateTracker.h @@ -72,7 +72,7 @@ private: int32_t mDefaultState = kStateUnknown; - int32_t mResetState; + int32_t mResetState = kStateUnknown; // Maps primary key to state value info std::unordered_map<HashableDimensionKey, StateValueInfo> mStateMap; diff --git a/cmds/statsd/tests/state/StateTracker_test.cpp b/cmds/statsd/tests/state/StateTracker_test.cpp index 26a3733ed598..84aaa54bc5bf 100644 --- a/cmds/statsd/tests/state/StateTracker_test.cpp +++ b/cmds/statsd/tests/state/StateTracker_test.cpp @@ -76,6 +76,23 @@ std::shared_ptr<LogEvent> buildUidProcessEvent(int uid, int state) { return event; } +// State with first uid in attribution chain as primary field - WakelockStateChanged +std::shared_ptr<LogEvent> buildPartialWakelockEvent(int uid, const std::string& tag, bool acquire) { + std::vector<AttributionNodeInternal> chain; + chain.push_back(AttributionNodeInternal()); + AttributionNodeInternal& attr = chain.back(); + attr.set_uid(uid); + + std::shared_ptr<LogEvent> event = + std::make_shared<LogEvent>(android::util::WAKELOCK_STATE_CHANGED, 1000 /* timestamp */); + event->write(chain); + event->write((int32_t)1); // PARTIAL_WAKE_LOCK + event->write(tag); + event->write(acquire ? 1 : 0); + event->init(); + return event; +} + // State with multiple primary fields - OverlayStateChanged std::shared_ptr<LogEvent> buildOverlayEvent(int uid, const std::string& packageName, int state) { std::shared_ptr<LogEvent> event = @@ -134,6 +151,39 @@ void getOverlayKey(int uid, string packageName, HashableDimensionKey* key) { key->addValue(FieldValue(field1, value1)); key->addValue(FieldValue(field2, value2)); } + +void getPartialWakelockKey(int uid, const std::string& tag, HashableDimensionKey* key) { + int pos1[] = {1, 1, 1}; + int pos3[] = {2, 0, 0}; + int pos4[] = {3, 0, 0}; + + Field field1(10 /* atom id */, pos1, 2 /* depth */); + + Field field3(10 /* atom id */, pos3, 0 /* depth */); + Field field4(10 /* atom id */, pos4, 0 /* depth */); + + Value value1((int32_t)uid); + Value value3((int32_t)1 /*partial*/); + Value value4(tag); + + key->addValue(FieldValue(field1, value1)); + key->addValue(FieldValue(field3, value3)); + key->addValue(FieldValue(field4, value4)); +} + +void getPartialWakelockKey(int uid, HashableDimensionKey* key) { + int pos1[] = {1, 1, 1}; + int pos3[] = {2, 0, 0}; + + Field field1(10 /* atom id */, pos1, 2 /* depth */); + Field field3(10 /* atom id */, pos3, 0 /* depth */); + + Value value1((int32_t)uid); + Value value3((int32_t)1 /*partial*/); + + key->addValue(FieldValue(field1, value1)); + key->addValue(FieldValue(field3, value3)); +} // END: get primary key functions TEST(StateListenerTest, TestStateListenerWeakPointer) { @@ -247,7 +297,8 @@ TEST(StateTrackerTest, TestStateChangeNoPrimaryFields) { // check StateTracker was updated by querying for state HashableDimensionKey queryKey = DEFAULT_DIMENSION_KEY; - EXPECT_EQ(2, getStateInt(mgr, android::util::SCREEN_STATE_CHANGED, queryKey)); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, + getStateInt(mgr, android::util::SCREEN_STATE_CHANGED, queryKey)); } /** @@ -272,7 +323,46 @@ TEST(StateTrackerTest, TestStateChangeOnePrimaryField) { // check StateTracker was updated by querying for state HashableDimensionKey queryKey; getUidProcessKey(1000 /* uid */, &queryKey); - EXPECT_EQ(1002, getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey)); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_TOP, + getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey)); +} + +TEST(StateTrackerTest, TestStateChangePrimaryFieldAttrChain) { + sp<TestStateListener> listener1 = new TestStateListener(); + StateManager mgr; + mgr.registerListener(android::util::WAKELOCK_STATE_CHANGED, listener1); + + // Log event. + std::shared_ptr<LogEvent> event = + buildPartialWakelockEvent(1001 /* uid */, "tag1", false /* acquire */); + mgr.onLogEvent(*event); + + EXPECT_EQ(1, mgr.getStateTrackersCount()); + EXPECT_EQ(1, mgr.getListenersCount(android::util::WAKELOCK_STATE_CHANGED)); + + // Check listener was updated. + EXPECT_EQ(1, listener1->updates.size()); + EXPECT_EQ(3, listener1->updates[0].mKey.getValues().size()); + EXPECT_EQ(1001, listener1->updates[0].mKey.getValues()[0].mValue.int_value); + EXPECT_EQ(1, listener1->updates[0].mKey.getValues()[1].mValue.int_value); + EXPECT_EQ("tag1", listener1->updates[0].mKey.getValues()[2].mValue.str_value); + EXPECT_EQ(WakelockStateChanged::RELEASE, listener1->updates[0].mState); + + // Check StateTracker was updated by querying for state. + HashableDimensionKey queryKey; + getPartialWakelockKey(1001 /* uid */, "tag1", &queryKey); + EXPECT_EQ(WakelockStateChanged::RELEASE, + getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey)); + + // No state stored for this query key. + HashableDimensionKey queryKey2; + getPartialWakelockKey(1002 /* uid */, "tag1", &queryKey2); + EXPECT_EQ(-1, getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey2)); + + // Partial query fails. + HashableDimensionKey queryKey3; + getPartialWakelockKey(1001 /* uid */, &queryKey3); + EXPECT_EQ(-1, getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey3)); } /** @@ -297,7 +387,8 @@ TEST(StateTrackerTest, TestStateChangeMultiplePrimaryFields) { // check StateTracker was updated by querying for state HashableDimensionKey queryKey; getOverlayKey(1000 /* uid */, "package1", &queryKey); - EXPECT_EQ(1, getStateInt(mgr, android::util::OVERLAY_STATE_CHANGED, queryKey)); + EXPECT_EQ(OverlayStateChanged::ENTERED, + getStateInt(mgr, android::util::OVERLAY_STATE_CHANGED, queryKey)); } /** @@ -326,10 +417,12 @@ TEST(StateTrackerTest, TestStateQuery) { sp<TestStateListener> listener1 = new TestStateListener(); sp<TestStateListener> listener2 = new TestStateListener(); sp<TestStateListener> listener3 = new TestStateListener(); + sp<TestStateListener> listener4 = new TestStateListener(); StateManager mgr; mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener1); mgr.registerListener(android::util::UID_PROCESS_STATE_CHANGED, listener2); mgr.registerListener(android::util::OVERLAY_STATE_CHANGED, listener3); + mgr.registerListener(android::util::WAKELOCK_STATE_CHANGED, listener4); std::shared_ptr<LogEvent> event1 = buildUidProcessEvent( 1000, @@ -346,8 +439,12 @@ TEST(StateTrackerTest, TestStateQuery) { android::app::ProcessStateEnum::PROCESS_STATE_TOP); // state value: 1002 std::shared_ptr<LogEvent> event5 = buildScreenEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON); - std::shared_ptr<LogEvent> event6 = buildOverlayEvent(1000, "package1", 1); - std::shared_ptr<LogEvent> event7 = buildOverlayEvent(1000, "package2", 2); + std::shared_ptr<LogEvent> event6 = + buildOverlayEvent(1000, "package1", OverlayStateChanged::ENTERED); + std::shared_ptr<LogEvent> event7 = + buildOverlayEvent(1000, "package2", OverlayStateChanged::EXITED); + std::shared_ptr<LogEvent> event8 = buildPartialWakelockEvent(1005, "tag1", true); + std::shared_ptr<LogEvent> event9 = buildPartialWakelockEvent(1005, "tag2", false); mgr.onLogEvent(*event1); mgr.onLogEvent(*event2); @@ -356,11 +453,14 @@ TEST(StateTrackerTest, TestStateQuery) { mgr.onLogEvent(*event5); mgr.onLogEvent(*event6); mgr.onLogEvent(*event7); + mgr.onLogEvent(*event8); + mgr.onLogEvent(*event9); // Query for UidProcessState of uid 1001 HashableDimensionKey queryKey1; getUidProcessKey(1001, &queryKey1); - EXPECT_EQ(1003, getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey1)); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE, + getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey1)); // Query for UidProcessState of uid 1004 - not in state map HashableDimensionKey queryKey2; @@ -370,15 +470,30 @@ TEST(StateTrackerTest, TestStateQuery) { // Query for UidProcessState of uid 1001 - after change in state mgr.onLogEvent(*event4); - EXPECT_EQ(1002, getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey1)); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_TOP, + getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey1)); // Query for ScreenState - EXPECT_EQ(2, getStateInt(mgr, android::util::SCREEN_STATE_CHANGED, DEFAULT_DIMENSION_KEY)); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, + getStateInt(mgr, android::util::SCREEN_STATE_CHANGED, DEFAULT_DIMENSION_KEY)); // Query for OverlayState of uid 1000, package name "package2" HashableDimensionKey queryKey3; getOverlayKey(1000, "package2", &queryKey3); - EXPECT_EQ(2, getStateInt(mgr, android::util::OVERLAY_STATE_CHANGED, queryKey3)); + EXPECT_EQ(OverlayStateChanged::EXITED, + getStateInt(mgr, android::util::OVERLAY_STATE_CHANGED, queryKey3)); + + // Query for WakelockState of uid 1005, tag 2 + HashableDimensionKey queryKey4; + getPartialWakelockKey(1005, "tag2", &queryKey4); + EXPECT_EQ(WakelockStateChanged::RELEASE, + getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey4)); + + // Query for WakelockState of uid 1005, tag 1 + HashableDimensionKey queryKey5; + getPartialWakelockKey(1005, "tag1", &queryKey5); + EXPECT_EQ(WakelockStateChanged::ACQUIRE, + getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey5)); } } // namespace statsd diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp index 7b651dfed7fc..e0aecceac4e3 100644 --- a/cmds/statsd/tests/statsd_test_util.cpp +++ b/cmds/statsd/tests/statsd_test_util.cpp @@ -617,19 +617,6 @@ int64_t StringToId(const string& str) { void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid) { EXPECT_EQ(value.field(), atomId); - // Attribution field. - EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1); - // Uid only. - EXPECT_EQ(value.value_tuple().dimensions_value(0) - .value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(value.value_tuple().dimensions_value(0) - .value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(value.value_tuple().dimensions_value(0) - .value_tuple().dimensions_value(0).value_int(), uid); -} - -void ValidateUidDimension(const DimensionsValue& value, int atomId, int uid) { - EXPECT_EQ(value.field(), atomId); EXPECT_EQ(value.value_tuple().dimensions_value_size(), 1); // Attribution field. EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1); diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index e46840c0f467..0bd8ce692884 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -17,6 +17,7 @@ package android.accessibilityservice; import android.accessibilityservice.GestureDescription.MotionEventGenerator; +import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -26,12 +27,15 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.Intent; import android.content.pm.ParceledListSlice; +import android.graphics.Bitmap; import android.graphics.Region; +import android.os.Binder; import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.RemoteCallback; import android.os.RemoteException; import android.util.ArrayMap; import android.util.Log; @@ -48,10 +52,13 @@ import android.view.accessibility.AccessibilityWindowInfo; import com.android.internal.os.HandlerCaller; import com.android.internal.os.SomeArgs; +import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; +import java.util.concurrent.Executor; +import java.util.function.Consumer; /** * Accessibility services should only be used to assist users with disabilities in using @@ -483,6 +490,9 @@ public abstract class AccessibilityService extends Service { private FingerprintGestureController mFingerprintGestureController; + /** @hide */ + public static final String KEY_ACCESSIBILITY_SCREENSHOT = "screenshot"; + /** * Callback for {@link android.view.accessibility.AccessibilityEvent}s. * @@ -1761,6 +1771,51 @@ public abstract class AccessibilityService extends Service { } /** + * Takes a screenshot of the specified display and returns it by {@link Bitmap.Config#HARDWARE} + * format. + * <p> + * <strong>Note:</strong> In order to take screenshot your service has + * to declare the capability to take screenshot by setting the + * {@link android.R.styleable#AccessibilityService_canTakeScreenshot} + * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}. + * Besides, This API is only supported for default display now + * {@link Display#DEFAULT_DISPLAY}. + * </p> + * + * @param displayId The logic display id, must be {@link Display#DEFAULT_DISPLAY} for + * default display. + * @param executor Executor on which to run the callback. + * @param callback The callback invoked when the taking screenshot is done. + * + * @return {@code true} if the taking screenshot accepted, {@code false} if not. + */ + public boolean takeScreenshot(int displayId, @NonNull @CallbackExecutor Executor executor, + @NonNull Consumer<Bitmap> callback) { + Preconditions.checkNotNull(executor, "executor cannot be null"); + Preconditions.checkNotNull(callback, "callback cannot be null"); + final IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance().getConnection( + mConnectionId); + if (connection == null) { + return false; + } + try { + connection.takeScreenshotWithCallback(displayId, new RemoteCallback((result) -> { + final Bitmap screenshot = result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT); + final long identity = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.accept(screenshot)); + } finally { + Binder.restoreCallingIdentity(identity); + } + })); + } catch (RemoteException re) { + throw new RuntimeException(re); + } + return true; + } + + /** * Implement to return the implementation of the internal accessibility * service interface. */ diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index 5e2c1faac156..12f2c3b17c96 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -86,6 +86,7 @@ import java.util.List; * @attr ref android.R.styleable#AccessibilityService_settingsActivity * @attr ref android.R.styleable#AccessibilityService_nonInteractiveUiTimeout * @attr ref android.R.styleable#AccessibilityService_interactiveUiTimeout + * @attr ref android.R.styleable#AccessibilityService_canTakeScreenshot * @see AccessibilityService * @see android.view.accessibility.AccessibilityEvent * @see android.view.accessibility.AccessibilityManager @@ -136,6 +137,12 @@ public class AccessibilityServiceInfo implements Parcelable { */ public static final int CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES = 0x00000040; + /** + * Capability: This accessibility service can take screenshot. + * @see android.R.styleable#AccessibilityService_canTakeScreenshot + */ + public static final int CAPABILITY_CAN_TAKE_SCREENSHOT = 0x00000080; + private static SparseArray<CapabilityInfo> sAvailableCapabilityInfos; /** @@ -625,6 +632,10 @@ public class AccessibilityServiceInfo implements Parcelable { .AccessibilityService_canRequestFingerprintGestures, false)) { mCapabilities |= CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES; } + if (asAttributes.getBoolean(com.android.internal.R.styleable + .AccessibilityService_canTakeScreenshot, false)) { + mCapabilities |= CAPABILITY_CAN_TAKE_SCREENSHOT; + } TypedValue peekedValue = asAttributes.peekValue( com.android.internal.R.styleable.AccessibilityService_description); if (peekedValue != null) { @@ -794,6 +805,7 @@ public class AccessibilityServiceInfo implements Parcelable { * @see #CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS * @see #CAPABILITY_CAN_CONTROL_MAGNIFICATION * @see #CAPABILITY_CAN_PERFORM_GESTURES + * @see #CAPABILITY_CAN_TAKE_SCREENSHOT */ public int getCapabilities() { return mCapabilities; @@ -810,6 +822,7 @@ public class AccessibilityServiceInfo implements Parcelable { * @see #CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS * @see #CAPABILITY_CAN_CONTROL_MAGNIFICATION * @see #CAPABILITY_CAN_PERFORM_GESTURES + * @see #CAPABILITY_CAN_TAKE_SCREENSHOT * * @hide */ @@ -1253,6 +1266,8 @@ public class AccessibilityServiceInfo implements Parcelable { return "CAPABILITY_CAN_PERFORM_GESTURES"; case CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES: return "CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES"; + case CAPABILITY_CAN_TAKE_SCREENSHOT: + return "CAPABILITY_CAN_TAKE_SCREENSHOT"; default: return "UNKNOWN"; } @@ -1314,6 +1329,10 @@ public class AccessibilityServiceInfo implements Parcelable { new CapabilityInfo(CAPABILITY_CAN_PERFORM_GESTURES, R.string.capability_title_canPerformGestures, R.string.capability_desc_canPerformGestures)); + sAvailableCapabilityInfos.put(CAPABILITY_CAN_TAKE_SCREENSHOT, + new CapabilityInfo(CAPABILITY_CAN_TAKE_SCREENSHOT, + R.string.capability_title_canTakeScreenshot, + R.string.capability_desc_canTakeScreenshot)); if ((context == null) || fingerprintAvailable(context)) { sAvailableCapabilityInfos.put(CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES, new CapabilityInfo(CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES, diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index 656f87fe435b..4ea5c62bf05b 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -18,8 +18,10 @@ package android.accessibilityservice; import android.accessibilityservice.AccessibilityServiceInfo; import android.content.pm.ParceledListSlice; +import android.graphics.Bitmap; import android.graphics.Region; import android.os.Bundle; +import android.os.RemoteCallback; import android.view.MagnificationSpec; import android.view.MotionEvent; import android.view.accessibility.AccessibilityNodeInfo; @@ -102,4 +104,10 @@ interface IAccessibilityServiceConnection { boolean isFingerprintGestureDetectionAvailable(); IBinder getOverlayWindowToken(int displayid); + + int getWindowIdForLeashToken(IBinder token); + + Bitmap takeScreenshot(int displayId); + + void takeScreenshotWithCallback(int displayId, in RemoteCallback callback); } diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index 8fe2f12a1023..f2702a864c2f 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -25,6 +25,7 @@ import android.annotation.SdkConstant.SdkConstantType; import android.annotation.Size; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.UserHandleAware; import android.app.Activity; import android.compat.annotation.UnsupportedAppUsage; import android.content.BroadcastReceiver; @@ -40,6 +41,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Parcelable; +import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.text.TextUtils; @@ -528,12 +530,9 @@ public class AccountManager { * authenticator known to the AccountManager service. Empty (never * null) if no authenticators are known. */ + @UserHandleAware public AuthenticatorDescription[] getAuthenticatorTypes() { - try { - return mService.getAuthenticatorTypes(UserHandle.getCallingUserId()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getAuthenticatorTypesAsUser(mContext.getUserId()); } /** @@ -584,13 +583,10 @@ public class AccountManager { * @return An array of {@link Account}, one for each account. Empty (never null) if no accounts * have been added. */ + @UserHandleAware @NonNull public Account[] getAccounts() { - try { - return mService.getAccounts(null, mContext.getOpPackageName()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getAccountsAsUser(mContext.getUserId()); } /** @@ -708,6 +704,7 @@ public class AccountManager { * @return An array of {@link Account}, one per matching account. Empty (never null) if no * accounts of the specified type have been added. */ + @UserHandleAware @NonNull public Account[] getAccountsByType(String type) { return getAccountsByTypeAsUser(type, mContext.getUser()); @@ -1183,23 +1180,11 @@ public class AccountManager { * {@link #removeAccount(Account, Activity, AccountManagerCallback, Handler)} * instead */ + @UserHandleAware @Deprecated public AccountManagerFuture<Boolean> removeAccount(final Account account, AccountManagerCallback<Boolean> callback, Handler handler) { - if (account == null) throw new IllegalArgumentException("account is null"); - return new Future2Task<Boolean>(handler, callback) { - @Override - public void doWork() throws RemoteException { - mService.removeAccount(mResponse, account, false); - } - @Override - public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException { - if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) { - throw new AuthenticatorException("no result in response"); - } - return bundle.getBoolean(KEY_BOOLEAN_RESULT); - } - }.start(); + return removeAccountAsUser(account, callback, handler, mContext.getUser()); } /** @@ -1243,15 +1228,10 @@ public class AccountManager { * adding accounts (of this type) has been disabled by policy * </ul> */ + @UserHandleAware public AccountManagerFuture<Bundle> removeAccount(final Account account, final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) { - if (account == null) throw new IllegalArgumentException("account is null"); - return new AmsTask(activity, handler, callback) { - @Override - public void doWork() throws RemoteException { - mService.removeAccount(mResponse, account, activity != null); - } - }.start(); + return removeAccountAsUser(account, activity, callback, handler, mContext.getUser()); } /** @@ -1841,24 +1821,30 @@ public class AccountManager { * creating a new account, usually because of network trouble * </ul> */ + @UserHandleAware public AccountManagerFuture<Bundle> addAccount(final String accountType, final String authTokenType, final String[] requiredFeatures, final Bundle addAccountOptions, final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) { - if (accountType == null) throw new IllegalArgumentException("accountType is null"); - final Bundle optionsIn = new Bundle(); - if (addAccountOptions != null) { - optionsIn.putAll(addAccountOptions); - } - optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName()); - - return new AmsTask(activity, handler, callback) { - @Override - public void doWork() throws RemoteException { - mService.addAccount(mResponse, accountType, authTokenType, - requiredFeatures, activity != null, optionsIn); + if (Process.myUserHandle().equals(mContext.getUser())) { + if (accountType == null) throw new IllegalArgumentException("accountType is null"); + final Bundle optionsIn = new Bundle(); + if (addAccountOptions != null) { + optionsIn.putAll(addAccountOptions); } - }.start(); + optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName()); + + return new AmsTask(activity, handler, callback) { + @Override + public void doWork() throws RemoteException { + mService.addAccount(mResponse, accountType, authTokenType, + requiredFeatures, activity != null, optionsIn); + } + }.start(); + } else { + return addAccountAsUser(accountType, authTokenType, requiredFeatures, addAccountOptions, + activity, callback, handler, mContext.getUser()); + } } /** @@ -2002,6 +1988,7 @@ public class AccountManager { * verifying the password, usually because of network trouble * </ul> */ + @UserHandleAware public AccountManagerFuture<Bundle> confirmCredentials(final Account account, final Bundle options, final Activity activity, @@ -3209,6 +3196,7 @@ public class AccountManager { * </ul> * @see #startAddAccountSession and #startUpdateCredentialsSession */ + @UserHandleAware public AccountManagerFuture<Bundle> finishSession( final Bundle sessionBundle, final Activity activity, diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl index 012713891d11..ce68e082cf4f 100644 --- a/core/java/android/accounts/IAccountManager.aidl +++ b/core/java/android/accounts/IAccountManager.aidl @@ -34,7 +34,6 @@ interface IAccountManager { String getPassword(in Account account); String getUserData(in Account account, String key); AuthenticatorDescription[] getAuthenticatorTypes(int userId); - Account[] getAccounts(String accountType, String opPackageName); Account[] getAccountsForPackage(String packageName, int uid, String opPackageName); Account[] getAccountsByTypeForPackage(String type, String packageName, String opPackageName); Account[] getAccountsAsUser(String accountType, int userId, String opPackageName); @@ -45,8 +44,6 @@ interface IAccountManager { void getAccountsByFeatures(in IAccountManagerResponse response, String accountType, in String[] features, String opPackageName); boolean addAccountExplicitly(in Account account, String password, in Bundle extras); - void removeAccount(in IAccountManagerResponse response, in Account account, - boolean expectActivityLaunch); void removeAccountAsUser(in IAccountManagerResponse response, in Account account, boolean expectActivityLaunch, int userId); boolean removeAccountExplicitly(in Account account); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 070a4f80d4e2..d952be5218a4 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -2539,7 +2539,8 @@ public class Activity extends ContextThemeWrapper mCalled = true; if (mAutoFillResetNeeded) { - getAutofillManager().onInvisibleForAutofill(); + // If stopped without changing the configurations, the response should expire. + getAutofillManager().onInvisibleForAutofill(!mChangingConfigurations); } else if (mIntent != null && mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN) && mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_CROSS_ACTIVITY)) { diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 9aef20b29490..2010cc98ef2d 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4064,6 +4064,7 @@ public class ActivityManager { * @hide */ @SystemApi + @TestApi @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean switchUser(@NonNull UserHandle user) { if (user == null) { @@ -4073,6 +4074,29 @@ public class ActivityManager { } /** + * Updates mcc mnc configuration and applies changes to the entire system. + * + * @param mcc mcc configuration to update. + * @param mnc mnc configuration to update. + * @throws RemoteException; IllegalArgumentException if mcc or mnc is null; + * @return Returns {@code true} if the configuration was updated successfully; + * {@code false} otherwise. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) + public boolean updateMccMncConfiguration(@NonNull String mcc, @NonNull String mnc) { + if (mcc == null || mnc == null) { + throw new IllegalArgumentException("mcc or mnc cannot be null."); + } + try { + return getService().updateMccMncConfiguration(mcc, mnc); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Logs out current current foreground user by switching to the system user and stopping the * user being switched from. * @hide diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 1c6a5615dbd5..d39a8c4699e8 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -133,6 +133,10 @@ public class ApplicationPackageManager extends PackageManager { // Default flags to use with PackageManager when no flags are given. private final static int sDefaultFlags = PackageManager.GET_SHARED_LIBRARY_FILES; + // Name of the resource which provides background permission button string + public static final String APP_PERMISSION_BUTTON_ALLOW_ALWAYS = + "app_permission_button_allow_always"; + private final Object mLock = new Object(); @GuardedBy("mLock") @@ -807,6 +811,26 @@ public class ApplicationPackageManager extends PackageManager { } @Override + public CharSequence getBackgroundPermissionButtonLabel() { + try { + + String permissionController = getPermissionControllerPackageName(); + Context context = + mContext.createPackageContext(permissionController, 0); + + int textId = context.getResources().getIdentifier(APP_PERMISSION_BUTTON_ALLOW_ALWAYS, + "string", "com.android.permissioncontroller"); +// permissionController); STOPSHIP b/147434671 + if (textId != 0) { + return context.getText(textId); + } + } catch (NameNotFoundException e) { + Log.e(TAG, "Permission controller not found.", e); + } + return ""; + } + + @Override public int checkSignatures(String pkg1, String pkg2) { try { return mPM.checkSignatures(pkg1, pkg2); diff --git a/core/java/android/app/DisabledWallpaperManager.java b/core/java/android/app/DisabledWallpaperManager.java index 518594191e6c..1f323c378093 100644 --- a/core/java/android/app/DisabledWallpaperManager.java +++ b/core/java/android/app/DisabledWallpaperManager.java @@ -41,8 +41,7 @@ final class DisabledWallpaperManager extends WallpaperManager { // Don't need to worry about synchronization private static DisabledWallpaperManager sInstance; - // TODO(b/138939803): STOPSHIP changed to false and/or remove it - private static final boolean DEBUG = true; + private static final boolean DEBUG = false; @NonNull static DisabledWallpaperManager getInstance() { @@ -53,7 +52,6 @@ final class DisabledWallpaperManager extends WallpaperManager { } private DisabledWallpaperManager() { - super(null, null, null); } @Override @@ -66,10 +64,6 @@ final class DisabledWallpaperManager extends WallpaperManager { return false; } - // TODO(b/138939803): STOPSHIP methods below should not be necessary, - // callers should check if isWallpaperSupported(), consider removing them to keep this class - // simpler - private static <T> T unsupported() { if (DEBUG) Log.w(TAG, "unsupported method called; returning null", new Exception()); return null; @@ -343,4 +337,9 @@ final class DisabledWallpaperManager extends WallpaperManager { public boolean isWallpaperBackupEligible(int which) { return unsupportedBoolean(); } + + @Override + public boolean wallpaperSupportsWcg(int which) { + return unsupportedBoolean(); + } } diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index e8494c4c5893..580382e0a4aa 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -188,6 +188,16 @@ interface IActivityManager { */ @UnsupportedAppUsage boolean updateConfiguration(in Configuration values); + /** + * Updates mcc mnc configuration and applies changes to the entire system. + * + * @param mcc mcc configuration to update. + * @param mnc mnc configuration to update. + * @throws RemoteException; IllegalArgumentException if mcc or mnc is null. + * @return Returns {@code true} if the configuration was updated; + * {@code false} otherwise. + */ + boolean updateMccMncConfiguration(in String mcc, in String mnc); boolean stopServiceToken(in ComponentName className, in IBinder token, int startId); @UnsupportedAppUsage void setProcessLimit(int max); @@ -345,6 +355,12 @@ interface IActivityManager { in Bundle options, int userId); @UnsupportedAppUsage int stopUser(int userid, boolean force, in IStopUserCallback callback); + /** + * Check {@link com.android.server.am.ActivityManagerService#stopUserWithDelayedLocking(int, boolean, IStopUserCallback)} + * for details. + */ + int stopUserWithDelayedLocking(int userid, boolean force, in IStopUserCallback callback); + @UnsupportedAppUsage void registerUserSwitchObserver(in IUserSwitchObserver observer, in String name); void unregisterUserSwitchObserver(in IUserSwitchObserver observer); diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index fcdb7cc0855d..7af7a4adc748 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -94,7 +94,7 @@ interface INotificationManager void updateNotificationChannelGroupForPackage(String pkg, int uid, in NotificationChannelGroup group); void updateNotificationChannelForPackage(String pkg, int uid, in NotificationChannel channel); NotificationChannel getNotificationChannel(String callingPkg, int userId, String pkg, String channelId); - NotificationChannel getConversationNotificationChannel(String callingPkg, int userId, String pkg, String channelId, String conversationId); + NotificationChannel getConversationNotificationChannel(String callingPkg, int userId, String pkg, String channelId, boolean returnParentIfNoConversationChannel, String conversationId); void createConversationNotificationChannelForPackage(String pkg, int uid, in NotificationChannel parentChannel, String conversationId); NotificationChannel getNotificationChannelForPackage(String pkg, int uid, String channelId, boolean includeDeleted); void deleteNotificationChannel(String pkg, String channelId); diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl index 8c3180b400ef..80ba464851e0 100644 --- a/core/java/android/app/IUiAutomationConnection.aidl +++ b/core/java/android/app/IUiAutomationConnection.aidl @@ -39,7 +39,6 @@ interface IUiAutomationConnection { boolean injectInputEvent(in InputEvent event, boolean sync); void syncInputTransactions(); boolean setRotation(int rotation); - Bitmap takeScreenshot(in Rect crop, int rotation); boolean clearWindowContentFrameStats(int windowId); WindowContentFrameStats getWindowContentFrameStats(int windowId); void clearWindowAnimationFrameStats(); diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 775b1d165717..0c5e67cd4c6a 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -251,7 +251,7 @@ public final class LoadedApk { } private AppComponentFactory createAppFactory(ApplicationInfo appInfo, ClassLoader cl) { - if (appInfo.appComponentFactory != null && cl != null) { + if (mIncludeCode && appInfo.appComponentFactory != null && cl != null) { try { return (AppComponentFactory) cl.loadClass(appInfo.appComponentFactory).newInstance(); @@ -464,6 +464,9 @@ public final class LoadedApk { || appDir.equals(instrumentedAppDir)) { outZipPaths.clear(); outZipPaths.add(instrumentationAppDir); + if (!instrumentationAppDir.equals(instrumentedAppDir)) { + outZipPaths.add(instrumentedAppDir); + } // Only add splits if the app did not request isolated split loading. if (!aInfo.requestsIsolatedSplitLoading()) { @@ -472,7 +475,6 @@ public final class LoadedApk { } if (!instrumentationAppDir.equals(instrumentedAppDir)) { - outZipPaths.add(instrumentedAppDir); if (instrumentedSplitAppDirs != null) { Collections.addAll(outZipPaths, instrumentedSplitAppDirs); } diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java index a33c2c19d05c..5a4622e0b245 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -15,6 +15,8 @@ */ package android.app; +import static android.annotation.SystemApi.Client.MODULE_APPS; + import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; @@ -31,6 +33,7 @@ import android.os.Parcelable; import android.provider.Settings; import android.service.notification.NotificationListenerService; import android.text.TextUtils; +import android.util.Log; import android.util.proto.ProtoOutputStream; import com.android.internal.util.Preconditions; @@ -71,7 +74,7 @@ public final class NotificationChannel implements Parcelable { * Conversation id to use for apps that aren't providing them yet. * @hide */ - public static final String PLACEHOLDER_CONVERSATION_ID = "placeholder_id"; + public static final String PLACEHOLDER_CONVERSATION_ID = ":placeholder_id"; /** * The maximum length for text fields in a NotificationChannel. Fields will be truncated at this @@ -104,6 +107,7 @@ public final class NotificationChannel implements Parcelable { private static final String ATT_ORIG_IMP = "orig_imp"; private static final String ATT_PARENT_CHANNEL = "parent"; private static final String ATT_CONVERSATION_ID = "conv_id"; + private static final String ATT_DEMOTE = "dem"; private static final String DELIMITER = ","; /** @@ -193,6 +197,7 @@ public final class NotificationChannel implements Parcelable { private boolean mImportanceLockedDefaultApp; private String mParentId = null; private String mConversationId = null; + private boolean mDemoted = false; /** * Creates a notification channel. @@ -259,6 +264,7 @@ public final class NotificationChannel implements Parcelable { mOriginalImportance = in.readInt(); mParentId = in.readString(); mConversationId = in.readString(); + mDemoted = in.readBoolean(); } @Override @@ -316,6 +322,7 @@ public final class NotificationChannel implements Parcelable { dest.writeInt(mOriginalImportance); dest.writeString(mParentId); dest.writeString(mConversationId); + dest.writeBoolean(mDemoted); } /** @@ -349,9 +356,13 @@ public final class NotificationChannel implements Parcelable { } /** + * Allows users to block notifications sent through this channel, if this channel belongs to + * a package that is signed with the system signature. If the channel does not belong to a + * package that is signed with the system signature, this method does nothing. + * @param blockableSystem if {@code true}, allows users to block notifications on this channel. * @hide */ - @UnsupportedAppUsage + @SystemApi(client = MODULE_APPS) @TestApi public void setBlockableSystem(boolean blockableSystem) { mBlockableSystem = blockableSystem; @@ -385,8 +396,6 @@ public final class NotificationChannel implements Parcelable { return input; } - // Modifiable by apps on channel creation. - /** * @hide */ @@ -394,6 +403,8 @@ public final class NotificationChannel implements Parcelable { mId = id; } + // Modifiable by apps on channel creation. + /** * Sets what group this channel belongs to. * @@ -766,6 +777,20 @@ public final class NotificationChannel implements Parcelable { } /** + * @hide + */ + public void setDemoted(boolean demoted) { + mDemoted = demoted; + } + + /** + * @hide + */ + public boolean isDemoted() { + return mDemoted; + } + + /** * Returns whether the user has chosen the importance of this channel, either to affirm the * initial selection from the app, or changed it to be higher or lower. * @see #getImportance() @@ -826,8 +851,9 @@ public final class NotificationChannel implements Parcelable { setBlockableSystem(safeBool(parser, ATT_BLOCKABLE_SYSTEM, false)); setAllowBubbles(safeBool(parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE)); setOriginalImportance(safeInt(parser, ATT_ORIG_IMP, DEFAULT_IMPORTANCE)); - setConversationId(parser.getAttributeValue(ATT_PARENT_CHANNEL, null), - parser.getAttributeValue(ATT_CONVERSATION_ID, null)); + setConversationId(parser.getAttributeValue(null, ATT_PARENT_CHANNEL), + parser.getAttributeValue(null, ATT_CONVERSATION_ID)); + setDemoted(safeBool(parser, ATT_DEMOTE, false)); } @Nullable @@ -958,6 +984,9 @@ public final class NotificationChannel implements Parcelable { if (getConversationId() != null) { out.attribute(null, ATT_CONVERSATION_ID, getConversationId()); } + if (isDemoted()) { + out.attribute(null, ATT_DEMOTE, Boolean.toString(isDemoted())); + } // mImportanceLockedDefaultApp and mImportanceLockedByOEM have a different source of // truth and so aren't written to this xml file @@ -1117,7 +1146,8 @@ public final class NotificationChannel implements Parcelable { && mImportanceLockedDefaultApp == that.mImportanceLockedDefaultApp && mOriginalImportance == that.mOriginalImportance && Objects.equals(getParentChannelId(), that.getParentChannelId()) - && Objects.equals(getConversationId(), that.getConversationId()); + && Objects.equals(getConversationId(), that.getConversationId()) + && isDemoted() == that.isDemoted(); } @Override @@ -1128,7 +1158,7 @@ public final class NotificationChannel implements Parcelable { isFgServiceShown(), mVibrationEnabled, mShowBadge, isDeleted(), getGroup(), getAudioAttributes(), isBlockableSystem(), mAllowBubbles, mImportanceLockedByOEM, mImportanceLockedDefaultApp, mOriginalImportance, - mParentId, mConversationId); + mParentId, mConversationId, mDemoted); result = 31 * result + Arrays.hashCode(mVibration); return result; } @@ -1175,7 +1205,8 @@ public final class NotificationChannel implements Parcelable { + ", mImportanceLockedDefaultApp=" + mImportanceLockedDefaultApp + ", mOriginalImp=" + mOriginalImportance + ", mParent=" + mParentId - + ", mConversationId" + mConversationId; + + ", mConversationId=" + mConversationId + + ", mDemoted=" + mDemoted; } /** @hide */ diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 61c109885e05..1a8e15c0a23e 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -855,7 +855,8 @@ public class NotificationManager { INotificationManager service = getService(); try { return service.getConversationNotificationChannel(mContext.getOpPackageName(), - mContext.getUserId(), mContext.getPackageName(), channelId, conversationId); + mContext.getUserId(), mContext.getPackageName(), channelId, true, + conversationId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 96664ebd87bd..7b21f12e04f1 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -54,6 +54,7 @@ import android.content.integrity.AppIntegrityManager; import android.content.integrity.IAppIntegrityManager; import android.content.om.IOverlayManager; import android.content.om.OverlayManager; +import android.content.pm.ApplicationInfo; import android.content.pm.CrossProfileApps; import android.content.pm.DataLoaderManager; import android.content.pm.ICrossProfileApps; @@ -346,6 +347,14 @@ public final class SystemServiceRegistry { } }); + registerService(Context.NETWORK_STACK_SERVICE, IBinder.class, + new StaticServiceFetcher<IBinder>() { + @Override + public IBinder createService() { + return ServiceManager.getService(Context.NETWORK_STACK_SERVICE); + } + }); + registerService(Context.TETHERING_SERVICE, TetheringManager.class, new CachedServiceFetcher<TetheringManager>() { @Override @@ -685,20 +694,20 @@ public final class SystemServiceRegistry { throws ServiceNotFoundException { final IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE); if (b == null) { - // There are 2 reason service can be null: - // 1.Device doesn't support it - that's fine - // 2.App is running on instant mode - should fail + ApplicationInfo appInfo = ctx.getApplicationInfo(); + if (appInfo.targetSdkVersion >= Build.VERSION_CODES.P + && appInfo.isInstantApp()) { + // Instant app + throw new ServiceNotFoundException(Context.WALLPAPER_SERVICE); + } final boolean enabled = Resources.getSystem() .getBoolean(com.android.internal.R.bool.config_enableWallpaperService); if (!enabled) { - // Life moves on... + // Device doesn't support wallpaper, return a limited manager return DisabledWallpaperManager.getInstance(); } - if (ctx.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.P) { - // Instant app - throw new ServiceNotFoundException(Context.WALLPAPER_SERVICE); - } // Bad state - WallpaperManager methods will throw exception + Log.e(TAG, "No wallpaper service"); } IWallpaperManager service = IWallpaperManager.Stub.asInterface(b); return new WallpaperManager(service, ctx.getOuterContext(), diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index 18a3e6e89552..2579bd1abbfd 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -27,10 +27,7 @@ import android.annotation.Nullable; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.Bitmap; -import android.graphics.Point; -import android.graphics.Rect; import android.graphics.Region; -import android.hardware.display.DisplayManagerGlobal; import android.os.Build; import android.os.Handler; import android.os.HandlerThread; @@ -831,39 +828,20 @@ public final class UiAutomation { } /** - * Takes a screenshot. + * Takes a screenshot of the default display and returns it by {@link Bitmap.Config#HARDWARE} + * format. * * @return The screenshot bitmap on success, null otherwise. */ public Bitmap takeScreenshot() { + final int connectionId; synchronized (mLock) { throwIfNotConnectedLocked(); + connectionId = mConnectionId; } - Display display = DisplayManagerGlobal.getInstance() - .getRealDisplay(Display.DEFAULT_DISPLAY); - Point displaySize = new Point(); - display.getRealSize(displaySize); - - int rotation = display.getRotation(); - - // Take the screenshot - Bitmap screenShot = null; - try { - // Calling out without a lock held. - screenShot = mUiAutomationConnection.takeScreenshot( - new Rect(0, 0, displaySize.x, displaySize.y), rotation); - if (screenShot == null) { - return null; - } - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error while taking screnshot!", re); - return null; - } - - // Optimization - screenShot.setHasAlpha(false); - - return screenShot; + // Calling out without a lock held. + return AccessibilityInteractionClient.getInstance() + .takeScreenshot(connectionId, Display.DEFAULT_DISPLAY); } /** diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java index 82e988109db8..4fb974305e48 100644 --- a/core/java/android/app/UiAutomationConnection.java +++ b/core/java/android/app/UiAutomationConnection.java @@ -21,8 +21,6 @@ import android.accessibilityservice.IAccessibilityServiceClient; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Rect; import android.hardware.input.InputManager; import android.os.Binder; import android.os.IBinder; @@ -180,23 +178,6 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { } @Override - public Bitmap takeScreenshot(Rect crop, int rotation) { - synchronized (mLock) { - throwIfCalledByNotTrustedUidLocked(); - throwIfShutdownLocked(); - throwIfNotConnectedLocked(); - } - final long identity = Binder.clearCallingIdentity(); - try { - int width = crop.width(); - int height = crop.height(); - return SurfaceControl.screenshot(crop, width, height, rotation); - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - @Override public boolean clearWindowContentFrameStats(int windowId) throws RemoteException { synchronized (mLock) { throwIfCalledByNotTrustedUidLocked(); @@ -449,7 +430,8 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY - | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS); + | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS + | AccessibilityServiceInfo.CAPABILITY_CAN_TAKE_SCREENSHOT); try { // Calling out with a lock held is fine since if the system // process is gone the client calling in will be killed. diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 2507991362a5..9ad3d38a3084 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -543,6 +543,13 @@ public class WallpaperManager { mCmProxy = new ColorManagementProxy(context); } + // no-op constructor called just by DisabledWallpaperManager + /*package*/ WallpaperManager() { + mContext = null; + mCmProxy = null; + mWcgEnabled = false; + } + /** * Retrieve a WallpaperManager associated with the given Context. */ @@ -568,8 +575,10 @@ public class WallpaperManager { * * @see Configuration#isScreenWideColorGamut() * @return True if wcg should be enabled for this device. + * @hide */ - private boolean shouldEnableWideColorGamut() { + @TestApi + public boolean shouldEnableWideColorGamut() { return mWcgEnabled; } @@ -877,6 +886,7 @@ public class WallpaperManager { * @see #FLAG_SYSTEM * @hide */ + @TestApi @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public boolean wallpaperSupportsWcg(int which) { if (!shouldEnableWideColorGamut()) { @@ -893,6 +903,8 @@ public class WallpaperManager { * * @hide */ + @TestApi + @Nullable @UnsupportedAppUsage public Bitmap getBitmap() { return getBitmap(false); diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 69640b8321d1..be8e1d60f290 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -1992,16 +1992,6 @@ public class DevicePolicyManager { public static final int CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14; /** - * Result code for {@link #checkProvisioningPreCondition}. - * - * <p>Returned for {@link #ACTION_PROVISION_MANAGED_PROFILE} when adding a managed profile is - * disallowed by {@link UserManager#DISALLOW_ADD_MANAGED_PROFILE}. - * - * @hide - */ - public static final int CODE_ADD_MANAGED_PROFILE_DISALLOWED = 15; - - /** * Result codes for {@link #checkProvisioningPreCondition} indicating all the provisioning pre * conditions. * @@ -2013,7 +2003,7 @@ public class DevicePolicyManager { CODE_USER_SETUP_COMPLETED, CODE_NOT_SYSTEM_USER, CODE_HAS_PAIRED, CODE_MANAGED_USERS_NOT_SUPPORTED, CODE_SYSTEM_USER, CODE_CANNOT_ADD_MANAGED_PROFILE, CODE_NOT_SYSTEM_USER_SPLIT, CODE_DEVICE_ADMIN_NOT_SUPPORTED, - CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER, CODE_ADD_MANAGED_PROFILE_DISALLOWED + CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER }) public @interface ProvisioningPreCondition {} @@ -6860,21 +6850,18 @@ public class DevicePolicyManager { } /** - * @hide - * Privileged apps can use this method to find out if the device was provisioned as + * Apps can use this method to find out if the device was provisioned as * organization-owend device with a managed profile. * * This, together with checking whether the device has a device owner (by calling - * {@link #isDeviceManaged()}), could be used to learn whether the device is owned by an + * {@link #isDeviceOwnerApp}), could be used to learn whether the device is owned by an * organization or an individual: - * If this method returns true OR {@link #isDeviceManaged()} returns true, then - * the device is owned by an organization. Otherwise, it's owned by an individual. + * If this method returns true OR {@link #isDeviceOwnerApp} returns true (for any package), + * then the device is owned by an organization. Otherwise, it's owned by an individual. * * @return {@code true} if the device was provisioned as organization-owned device, * {@code false} otherwise. */ - @SystemApi - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isOrganizationOwnedDeviceWithManagedProfile() { throwIfParentInstance("isOrganizationOwnedDeviceWithManagedProfile"); if (mService != null) { @@ -7428,7 +7415,9 @@ public class DevicePolicyManager { * @param userHandle The user for whom to check the caller-id permission * @hide */ - public boolean getBluetoothContactSharingDisabled(UserHandle userHandle) { + @SystemApi + @RequiresPermission(permission.INTERACT_ACROSS_USERS) + public boolean getBluetoothContactSharingDisabled(@NonNull UserHandle userHandle) { if (mService != null) { try { return mService.getBluetoothContactSharingDisabledForUser(userHandle diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java index c8f2ff34a70c..567eb4a68a1d 100644 --- a/core/java/android/app/backup/BackupTransport.java +++ b/core/java/android/app/backup/BackupTransport.java @@ -85,6 +85,15 @@ public class BackupTransport { public static final int FLAG_NON_INCREMENTAL = 1 << 2; /** + * For key value backup, indicates that the backup contains no new data since the last backup + * attempt completed without any errors. The transport should use this to record that + * a successful backup attempt has been completed but no backup data has been changed. + * + * @see #performBackup(PackageInfo, ParcelFileDescriptor, int) + */ + public static final int FLAG_DATA_NOT_CHANGED = 1 << 3; + + /** * Used as a boolean extra in the binding intent of transports. We pass {@code true} to * notify transports that the current connection is used for registering the transport. */ @@ -302,7 +311,8 @@ public class BackupTransport { * BackupService.doBackup() method. This may be a pipe rather than a file on * persistent media, so it may not be seekable. * @param flags a combination of {@link BackupTransport#FLAG_USER_INITIATED}, {@link - * BackupTransport#FLAG_NON_INCREMENTAL}, {@link BackupTransport#FLAG_INCREMENTAL}, or 0. + * BackupTransport#FLAG_NON_INCREMENTAL}, {@link BackupTransport#FLAG_INCREMENTAL}, + * {@link BackupTransport#FLAG_DATA_NOT_CHANGED},or 0. * @return one of {@link BackupTransport#TRANSPORT_OK} (OK so far), * {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED} (to suppress backup of this * specific package, but allow others to proceed), diff --git a/core/java/android/app/timedetector/PhoneTimeSuggestion.java b/core/java/android/app/timedetector/PhoneTimeSuggestion.java index 479e4b4efb4c..bd649f88f40a 100644 --- a/core/java/android/app/timedetector/PhoneTimeSuggestion.java +++ b/core/java/android/app/timedetector/PhoneTimeSuggestion.java @@ -18,6 +18,7 @@ package android.app.timedetector; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import android.os.TimestampedValue; @@ -28,17 +29,23 @@ import java.util.List; import java.util.Objects; /** - * A time signal from a telephony source. The value can be {@code null} to indicate that the - * telephony source has entered an "un-opinionated" state and any previously sent suggestions are - * being withdrawn. When not {@code null}, the value consists of the number of milliseconds elapsed - * since 1/1/1970 00:00:00 UTC and the time according to the elapsed realtime clock when that number - * was established. The elapsed realtime clock is considered accurate but volatile, so time signals - * must not be persisted across device resets. + * A time suggestion from an identified telephony source. e.g. from NITZ information from a specific + * radio. + * + * <p>The time value can be {@code null} to indicate that the telephony source has entered an + * "un-opinionated" state and any previous suggestions from the source are being withdrawn. When not + * {@code null}, the value consists of the number of milliseconds elapsed since 1/1/1970 00:00:00 + * UTC and the time according to the elapsed realtime clock when that number was established. The + * elapsed realtime clock is considered accurate but volatile, so time suggestions must not be + * persisted across device resets. * * @hide */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public final class PhoneTimeSuggestion implements Parcelable { + /** @hide */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final @NonNull Parcelable.Creator<PhoneTimeSuggestion> CREATOR = new Parcelable.Creator<PhoneTimeSuggestion>() { public PhoneTimeSuggestion createFromParcel(Parcel in) { @@ -85,15 +92,27 @@ public final class PhoneTimeSuggestion implements Parcelable { dest.writeList(mDebugInfo); } + /** + * Returns an identifier for the source of this suggestion. When a device has several "phones", + * i.e. sim slots or equivalent, it is used to identify which one. + */ public int getPhoneId() { return mPhoneId; } + /** + * Returns the suggestion. {@code null} means that the caller is no longer sure what time it + * is. + */ @Nullable public TimestampedValue<Long> getUtcTime() { return mUtcTime; } + /** + * Returns debug metadata for the suggestion. The information is present in {@link #toString()} + * but is not considered for {@link #equals(Object)} and {@link #hashCode()}. + */ @NonNull public List<String> getDebugInfo() { return mDebugInfo == null @@ -105,7 +124,7 @@ public final class PhoneTimeSuggestion implements Parcelable { * information is present in {@link #toString()} but is not considered for * {@link #equals(Object)} and {@link #hashCode()}. */ - public void addDebugInfo(String debugInfo) { + public void addDebugInfo(@NonNull String debugInfo) { if (mDebugInfo == null) { mDebugInfo = new ArrayList<>(); } @@ -156,16 +175,19 @@ public final class PhoneTimeSuggestion implements Parcelable { * * @hide */ - public static class Builder { + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final class Builder { private final int mPhoneId; - private TimestampedValue<Long> mUtcTime; - private List<String> mDebugInfo; + @Nullable private TimestampedValue<Long> mUtcTime; + @Nullable private List<String> mDebugInfo; + /** Creates a builder with the specified {@code phoneId}. */ public Builder(int phoneId) { mPhoneId = phoneId; } /** Returns the builder for call chaining. */ + @NonNull public Builder setUtcTime(@Nullable TimestampedValue<Long> utcTime) { if (utcTime != null) { // utcTime can be null, but the value it holds cannot. @@ -177,6 +199,7 @@ public final class PhoneTimeSuggestion implements Parcelable { } /** Returns the builder for call chaining. */ + @NonNull public Builder addDebugInfo(@NonNull String debugInfo) { if (mDebugInfo == null) { mDebugInfo = new ArrayList<>(); @@ -186,6 +209,7 @@ public final class PhoneTimeSuggestion implements Parcelable { } /** Returns the {@link PhoneTimeSuggestion}. */ + @NonNull public PhoneTimeSuggestion build() { return new PhoneTimeSuggestion(this); } diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java index 54dd1bed5361..7c29f017c02b 100644 --- a/core/java/android/app/timedetector/TimeDetector.java +++ b/core/java/android/app/timedetector/TimeDetector.java @@ -18,6 +18,7 @@ package android.app.timedetector; import android.annotation.NonNull; import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; import android.os.RemoteException; @@ -29,8 +30,11 @@ import android.util.Log; /** * The interface through which system components can send signals to the TimeDetectorService. + * + * <p>This class is marked non-final for mockito. * @hide */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @SystemService(Context.TIME_DETECTOR_SERVICE) public class TimeDetector { private static final String TAG = "timedetector.TimeDetector"; @@ -38,6 +42,7 @@ public class TimeDetector { private final ITimeDetectorService mITimeDetectorService; + /** @hide */ public TimeDetector() throws ServiceNotFoundException { mITimeDetectorService = ITimeDetectorService.Stub.asInterface( ServiceManager.getServiceOrThrow(Context.TIME_DETECTOR_SERVICE)); @@ -62,6 +67,8 @@ public class TimeDetector { /** * Suggests the user's manually entered current time to the detector. + * + * @hide */ @RequiresPermission(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE) public void suggestManualTime(@NonNull ManualTimeSuggestion timeSuggestion) { @@ -77,6 +84,8 @@ public class TimeDetector { /** * A shared utility method to create a {@link ManualTimeSuggestion}. + * + * @hide */ public static ManualTimeSuggestion createManualTimeSuggestion(long when, String why) { TimestampedValue<Long> utcTime = @@ -88,6 +97,8 @@ public class TimeDetector { /** * Suggests the time according to a network time source like NTP. + * + * @hide */ @RequiresPermission(android.Manifest.permission.SET_TIME) public void suggestNetworkTime(NetworkTimeSuggestion timeSuggestion) { diff --git a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java b/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java index e8162488394c..d71ffcb9f772 100644 --- a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java +++ b/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java @@ -19,6 +19,7 @@ package android.app.timezonedetector; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -30,12 +31,27 @@ import java.util.List; import java.util.Objects; /** - * A suggested time zone from a Phone-based signal, e.g. from MCC and NITZ information. + * A time zone suggestion from an identified telephony source, e.g. from MCC and NITZ information + * associated with a specific radio. + * + * <p>The time zone ID can be {@code null} to indicate that the telephony source has entered an + * "un-opinionated" state and any previous suggestions from that source are being withdrawn. + * When not {@code null}, the value consists of a suggested time zone ID and metadata that can be + * used to judge quality / certainty of the suggestion. + * + * <p>{@code matchType} must be set to {@link #MATCH_TYPE_NA} when {@code zoneId} is {@code null}, + * and one of the other {@code MATCH_TYPE_} values when it is not {@code null}. + * + * <p>{@code quality} must be set to {@link #QUALITY_NA} when {@code zoneId} is {@code null}, + * and one of the other {@code QUALITY_} values when it is not {@code null}. * * @hide */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public final class PhoneTimeZoneSuggestion implements Parcelable { + /** @hide */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @NonNull public static final Creator<PhoneTimeZoneSuggestion> CREATOR = new Creator<PhoneTimeZoneSuggestion>() { @@ -58,6 +74,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { return new Builder(phoneId).addDebugInfo(debugInfo).build(); } + /** @hide */ @IntDef({ MATCH_TYPE_NA, MATCH_TYPE_NETWORK_COUNTRY_ONLY, MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, MATCH_TYPE_EMULATOR_ZONE_ID, MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY }) @Retention(RetentionPolicy.SOURCE) @@ -90,6 +107,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { */ public static final int MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY = 5; + /** @hide */ @IntDef({ QUALITY_NA, QUALITY_SINGLE_ZONE, QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS }) @Retention(RetentionPolicy.SOURCE) @@ -115,7 +133,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { /** * The ID of the phone this suggestion is associated with. For multiple-sim devices this - * helps to establish origin so filtering / stickiness can be implemented. + * helps to establish source so filtering / stickiness can be implemented. */ private final int mPhoneId; @@ -123,6 +141,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { * The suggestion. {@code null} means there is no current suggestion and any previous suggestion * should be forgotten. */ + @Nullable private final String mZoneId; /** @@ -139,9 +158,10 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { private final int mQuality; /** - * Free-form debug information about how the signal was derived. Used for debug only, + * Free-form debug information about how the suggestion was derived. Used for debug only, * intentionally not used in equals(), etc. */ + @Nullable private List<String> mDebugInfo; private PhoneTimeZoneSuggestion(Builder builder) { @@ -182,25 +202,47 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { return 0; } + /** + * Returns an identifier for the source of this suggestion. When a device has several "phones", + * i.e. sim slots or equivalent, it is used to identify which one. + */ public int getPhoneId() { return mPhoneId; } + /** + * Returns the suggested time zone Olson ID, e.g. "America/Los_Angeles". {@code null} means that + * the caller is no longer sure what the current time zone is. See + * {@link PhoneTimeZoneSuggestion} for the associated {@code matchType} / {@code quality} rules. + */ @Nullable public String getZoneId() { return mZoneId; } + /** + * Returns information about how the suggestion was determined which could be used to rank + * suggestions when several are available from different sources. See + * {@link PhoneTimeZoneSuggestion} for the associated rules. + */ @MatchType public int getMatchType() { return mMatchType; } + /** + * Returns information about the likelihood of the suggested zone being correct. See + * {@link PhoneTimeZoneSuggestion} for the associated rules. + */ @Quality public int getQuality() { return mQuality; } + /** + * Returns debug metadata for the suggestion. The information is present in {@link #toString()} + * but is not considered for {@link #equals(Object)} and {@link #hashCode()}. + */ @NonNull public List<String> getDebugInfo() { return mDebugInfo == null @@ -267,36 +309,43 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { * * @hide */ - public static class Builder { + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final class Builder { private final int mPhoneId; - private String mZoneId; + @Nullable private String mZoneId; @MatchType private int mMatchType; @Quality private int mQuality; - private List<String> mDebugInfo; + @Nullable private List<String> mDebugInfo; public Builder(int phoneId) { mPhoneId = phoneId; } - /** Returns the builder for call chaining. */ - public Builder setZoneId(String zoneId) { + /** + * Returns the builder for call chaining. + */ + @NonNull + public Builder setZoneId(@Nullable String zoneId) { mZoneId = zoneId; return this; } /** Returns the builder for call chaining. */ + @NonNull public Builder setMatchType(@MatchType int matchType) { mMatchType = matchType; return this; } /** Returns the builder for call chaining. */ + @NonNull public Builder setQuality(@Quality int quality) { mQuality = quality; return this; } /** Returns the builder for call chaining. */ + @NonNull public Builder addDebugInfo(@NonNull String debugInfo) { if (mDebugInfo == null) { mDebugInfo = new ArrayList<>(); @@ -333,6 +382,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { } /** Returns the {@link PhoneTimeZoneSuggestion}. */ + @NonNull public PhoneTimeZoneSuggestion build() { validate(); return new PhoneTimeZoneSuggestion(this); diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java index e165d8a76caa..5b5f311264e3 100644 --- a/core/java/android/app/timezonedetector/TimeZoneDetector.java +++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java @@ -18,6 +18,7 @@ package android.app.timezonedetector; import android.annotation.NonNull; import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; import android.os.RemoteException; @@ -28,8 +29,10 @@ import android.util.Log; /** * The interface through which system components can send signals to the TimeZoneDetectorService. * + * <p>This class is non-final for mockito. * @hide */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @SystemService(Context.TIME_ZONE_DETECTOR_SERVICE) public class TimeZoneDetector { private static final String TAG = "timezonedetector.TimeZoneDetector"; @@ -37,6 +40,7 @@ public class TimeZoneDetector { private final ITimeZoneDetectorService mITimeZoneDetectorService; + /** @hide */ public TimeZoneDetector() throws ServiceNotFoundException { mITimeZoneDetectorService = ITimeZoneDetectorService.Stub.asInterface( ServiceManager.getServiceOrThrow(Context.TIME_ZONE_DETECTOR_SERVICE)); @@ -46,7 +50,10 @@ public class TimeZoneDetector { * Suggests the current time zone, determined using telephony signals, to the detector. The * detector may ignore the signal based on system settings, whether better information is * available, and so on. + * + * @hide */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @RequiresPermission(android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE) public void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion timeZoneSuggestion) { if (DEBUG) { @@ -62,6 +69,8 @@ public class TimeZoneDetector { /** * Suggests the current time zone, determined for the user's manually information, to the * detector. The detector may ignore the signal based on system settings. + * + * @hide */ @RequiresPermission(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE) public void suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion timeZoneSuggestion) { @@ -77,6 +86,8 @@ public class TimeZoneDetector { /** * A shared utility method to create a {@link ManualTimeZoneSuggestion}. + * + * @hide */ public static ManualTimeZoneSuggestion createManualTimeZoneSuggestion(String tzId, String why) { ManualTimeZoneSuggestion suggestion = new ManualTimeZoneSuggestion(tzId); diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java index d840c1cc7248..6ab880dfb36d 100644 --- a/core/java/android/app/usage/UsageEvents.java +++ b/core/java/android/app/usage/UsageEvents.java @@ -451,21 +451,7 @@ public final class UsageEvents implements Parcelable { /** @hide */ public Event(Event orig) { - mPackage = orig.mPackage; - mClass = orig.mClass; - mInstanceId = orig.mInstanceId; - mTaskRootPackage = orig.mTaskRootPackage; - mTaskRootClass = orig.mTaskRootClass; - mTimeStamp = orig.mTimeStamp; - mEventType = orig.mEventType; - mConfiguration = orig.mConfiguration; - mShortcutId = orig.mShortcutId; - mAction = orig.mAction; - mContentType = orig.mContentType; - mContentAnnotations = orig.mContentAnnotations; - mFlags = orig.mFlags; - mBucketAndReason = orig.mBucketAndReason; - mNotificationChannelId = orig.mNotificationChannelId; + copyFrom(orig); } /** @@ -622,6 +608,24 @@ public final class UsageEvents implements Parcelable { // which instant apps can't use anyway, so there's no need to hide them. return ret; } + + private void copyFrom(Event orig) { + mPackage = orig.mPackage; + mClass = orig.mClass; + mInstanceId = orig.mInstanceId; + mTaskRootPackage = orig.mTaskRootPackage; + mTaskRootClass = orig.mTaskRootClass; + mTimeStamp = orig.mTimeStamp; + mEventType = orig.mEventType; + mConfiguration = orig.mConfiguration; + mShortcutId = orig.mShortcutId; + mAction = orig.mAction; + mContentType = orig.mContentType; + mContentAnnotations = orig.mContentAnnotations; + mFlags = orig.mFlags; + mBucketAndReason = orig.mBucketAndReason; + mNotificationChannelId = orig.mNotificationChannelId; + } } // Only used when creating the resulting events. Not used for reading/unparceling. @@ -725,10 +729,14 @@ public final class UsageEvents implements Parcelable { return false; } - readEventFromParcel(mParcel, eventOut); + if (mParcel != null) { + readEventFromParcel(mParcel, eventOut); + } else { + eventOut.copyFrom(mEventsToWrite.get(mIndex)); + } mIndex++; - if (mIndex >= mEventCount) { + if (mIndex >= mEventCount && mParcel != null) { mParcel.recycle(); mParcel = null; } diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java index 176a181965ed..a60e591dd0e6 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -163,7 +163,6 @@ public final class UsageStatsManager { /** * The app spent sufficient time in the old bucket without any substantial event so it reached * the timeout threshold to have its bucket lowered. - * * @hide */ public static final int REASON_MAIN_TIMEOUT = 0x0200; @@ -173,15 +172,25 @@ public final class UsageStatsManager { */ public static final int REASON_MAIN_USAGE = 0x0300; /** - * Forced by a core UID. + * Forced by the user/developer, either explicitly or implicitly through some action. If user + * action was not involved and this is purely due to the system, + * {@link #REASON_MAIN_FORCED_BY_SYSTEM} should be used instead. * @hide */ - public static final int REASON_MAIN_FORCED = 0x0400; + public static final int REASON_MAIN_FORCED_BY_USER = 0x0400; /** - * Set by a privileged system app. + * Set by a privileged system app. This may be overridden by + * {@link #REASON_MAIN_FORCED_BY_SYSTEM} or user action. * @hide */ public static final int REASON_MAIN_PREDICTED = 0x0500; + /** + * Forced by the system, independent of user action. If user action is involved, + * {@link #REASON_MAIN_FORCED_BY_USER} should be used instead. When this is used, only + * {@link #REASON_MAIN_FORCED_BY_SYSTEM} or user action can change the bucket. + * @hide + */ + public static final int REASON_MAIN_FORCED_BY_SYSTEM = 0x0600; /** @hide */ public static final int REASON_SUB_MASK = 0x00FF; @@ -1016,7 +1025,10 @@ public final class UsageStatsManager { case REASON_MAIN_DEFAULT: sb.append("d"); break; - case REASON_MAIN_FORCED: + case REASON_MAIN_FORCED_BY_SYSTEM: + sb.append("s"); + break; + case REASON_MAIN_FORCED_BY_USER: sb.append("f"); break; case REASON_MAIN_PREDICTED: diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 1b40a184c52b..679de8a281c3 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -1825,8 +1825,9 @@ public abstract class Context { * @throws ActivityNotFoundException * @hide */ - @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) + @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) @SystemApi + @TestApi public void startActivityAsUser(@RequiresPermission @NonNull Intent intent, @NonNull UserHandle user) { throw new RuntimeException("Not implemented. Must override in a subclass."); @@ -1873,7 +1874,7 @@ public abstract class Context { * @throws ActivityNotFoundException * @hide */ - @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) + @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) @UnsupportedAppUsage public void startActivityAsUser(@RequiresPermission Intent intent, @Nullable Bundle options, UserHandle userId) { @@ -1979,7 +1980,7 @@ public abstract class Context { * @see #startActivities(Intent[]) * @see PackageManager#resolveActivity */ - @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) + @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public int startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) { throw new RuntimeException("Not implemented. Must override in a subclass."); } @@ -3953,10 +3954,12 @@ public abstract class Context { /** * Use with {@link android.os.ServiceManager.getService()} to retrieve a - * {@link NetworkStackClient} IBinder for communicating with the network stack + * {@link INetworkStackConnector} IBinder for communicating with the network stack * @hide * @see NetworkStackClient */ + @SystemApi + @TestApi public static final String NETWORK_STACK_SERVICE = "network_stack"; /** diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index c8f587f71bca..ee758024fb9f 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -752,6 +752,22 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_PICK = "android.intent.action.PICK"; /** + * Activity Action: Creates a reminder. + * <p>Input: {@link #EXTRA_TITLE} The title of the reminder that will be shown to the user. + * {@link #EXTRA_TEXT} The reminder text that will be shown to the user. The intent should at + * least specify a title or a text. {@link #EXTRA_TIME} The time when the reminder will be shown + * to the user. The time is specified in milliseconds since the Epoch (optional). + * </p> + * <p>Output: Nothing.</p> + * + * @see #EXTRA_TITLE + * @see #EXTRA_TEXT + * @see #EXTRA_TIME + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_CREATE_REMINDER = "android.intent.action.CREATE_REMINDER"; + + /** * Activity Action: Creates a shortcut. * <p>Input: Nothing.</p> * <p>Output: An Intent representing the {@link android.content.pm.ShortcutInfo} result.</p> @@ -5726,6 +5742,15 @@ public class Intent implements Parcelable, Cloneable { = "android.intent.extra.SHUTDOWN_USERSPACE_ONLY"; /** + * Optional extra specifying a time in milliseconds since the Epoch. The value must be + * non-negative. + * <p> + * Type: long + * </p> + */ + public static final String EXTRA_TIME = "android.intent.extra.TIME"; + + /** * Optional int extra for {@link #ACTION_TIME_CHANGED} that indicates the * user has set their time format preference. See {@link #EXTRA_TIME_PREF_VALUE_USE_12_HOUR}, * {@link #EXTRA_TIME_PREF_VALUE_USE_24_HOUR} and diff --git a/core/java/android/content/pm/InstallSourceInfo.java b/core/java/android/content/pm/InstallSourceInfo.java index 4d235f1af2f7..c0fdcc900577 100644 --- a/core/java/android/content/pm/InstallSourceInfo.java +++ b/core/java/android/content/pm/InstallSourceInfo.java @@ -29,32 +29,39 @@ public final class InstallSourceInfo implements Parcelable { @Nullable private final String mInitiatingPackageName; + @Nullable private final SigningInfo mInitiatingPackageSigningInfo; + @Nullable private final String mOriginatingPackageName; @Nullable private final String mInstallingPackageName; /** @hide */ public InstallSourceInfo(@Nullable String initiatingPackageName, + @Nullable SigningInfo initiatingPackageSigningInfo, @Nullable String originatingPackageName, @Nullable String installingPackageName) { - this.mInitiatingPackageName = initiatingPackageName; - this.mOriginatingPackageName = originatingPackageName; - this.mInstallingPackageName = installingPackageName; + mInitiatingPackageName = initiatingPackageName; + mInitiatingPackageSigningInfo = initiatingPackageSigningInfo; + mOriginatingPackageName = originatingPackageName; + mInstallingPackageName = installingPackageName; } @Override public int describeContents() { - return 0; + return mInitiatingPackageSigningInfo == null + ? 0 : mInitiatingPackageSigningInfo.describeContents(); } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeString(mInitiatingPackageName); + dest.writeParcelable(mInitiatingPackageSigningInfo, flags); dest.writeString(mOriginatingPackageName); dest.writeString(mInstallingPackageName); } private InstallSourceInfo(Parcel source) { mInitiatingPackageName = source.readString(); + mInitiatingPackageSigningInfo = source.readParcelable(SigningInfo.class.getClassLoader()); mOriginatingPackageName = source.readString(); mInstallingPackageName = source.readString(); } @@ -66,6 +73,14 @@ public final class InstallSourceInfo implements Parcelable { } /** + * Information about the signing certificates used to sign the initiating package, if available. + */ + @Nullable + public SigningInfo getInitiatingPackageSigningInfo() { + return mInitiatingPackageSigningInfo; + } + + /** * The name of the package on behalf of which the initiating package requested the installation, * or null if not available. * <p> diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 4bfc40e698b9..1f502a101290 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -3379,6 +3379,10 @@ public abstract class PackageManager { * etc. This library is versioned and backwards compatible. Clients * should check its version via {@link android.ext.services.Version * #getVersionCode()} and avoid calling APIs added in later versions. + * <p> + * This shared library no longer exists since Android R. + * + * @see #getServicesSystemSharedLibraryPackageName() * * @hide */ @@ -4389,6 +4393,18 @@ public abstract class PackageManager { public abstract boolean shouldShowRequestPermissionRationale(@NonNull String permName); /** + * Gets the string that is displayed on the button which corresponds to granting background + * location in settings. The intended use for this is to help apps instruct users how to + * grant a background permission by providing the string that users will see. + * + * @return the string shown on the button for granting background location + */ + @NonNull + public CharSequence getBackgroundPermissionButtonLabel() { + return ""; + } + + /** * Returns an {@link android.content.Intent} suitable for passing to * {@link android.app.Activity#startActivityForResult(android.content.Intent, int)} * which prompts the user to grant permissions to this application. @@ -4733,6 +4749,9 @@ public abstract class PackageManager { /** * Get the name of the package hosting the services shared library. + * <p> + * Note that this package is no longer a shared library since Android R. It is now a package + * that hosts for a bunch of updatable services that the system binds to. * * @return The library host package. * @@ -6053,6 +6072,11 @@ public abstract class PackageManager { * If the calling application does not hold the INSTALL_PACKAGES permission then * the result will always return {@code null} from * {@link InstallSourceInfo#getOriginatingPackageName()}. + * <p> + * If the package that requested the install has been uninstalled, then information about it + * will only be returned from {@link InstallSourceInfo#getInitiatingPackageName()} and + * {@link InstallSourceInfo#getInitiatingPackageSigningInfo()} if the calling package is + * requesting its own install information and is not an instant app. * * @param packageName The name of the package to query * @throws NameNotFoundException if the given package name is not installed diff --git a/core/java/android/content/pm/parsing/ApkParseUtils.java b/core/java/android/content/pm/parsing/ApkParseUtils.java index a001ada8df4a..38d3137200c6 100644 --- a/core/java/android/content/pm/parsing/ApkParseUtils.java +++ b/core/java/android/content/pm/parsing/ApkParseUtils.java @@ -65,6 +65,7 @@ import android.os.FileUtils; import android.os.RemoteException; import android.os.SystemProperties; import android.os.Trace; +import android.os.ext.SdkExtensions; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; @@ -1615,11 +1616,72 @@ public class ApkParseUtils { ); } + int type; + final int innerDepth = parser.getDepth(); + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + if (parser.getName().equals("extension-sdk")) { + final ParseResult result = + parseExtensionSdk(parseInput, parsingPackage, res, parser); + if (!result.isSuccess()) { + return result; + } + } else { + Slog.w(TAG, "Unknown element under <uses-sdk>: " + parser.getName() + + " at " + parsingPackage.getBaseCodePath() + " " + + parser.getPositionDescription()); + } + XmlUtils.skipCurrentTag(parser); + } + parsingPackage.setMinSdkVersion(minSdkVersion) .setTargetSdkVersion(targetSdkVersion); } + return parseInput.success(parsingPackage); + } - XmlUtils.skipCurrentTag(parser); + private static ParseResult parseExtensionSdk( + ParseInput parseInput, + ParsingPackage parsingPackage, + Resources res, + XmlResourceParser parser + ) throws IOException, XmlPullParserException { + TypedArray sa = res.obtainAttributes(parser, + com.android.internal.R.styleable.AndroidManifestExtensionSdk); + int sdkVersion = sa.getInt( + com.android.internal.R.styleable.AndroidManifestExtensionSdk_sdkVersion, -1); + int minVersion = sa.getInt( + com.android.internal.R.styleable.AndroidManifestExtensionSdk_minExtensionVersion, + -1); + sa.recycle(); + + if (sdkVersion < 0) { + return parseInput.error( + PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, + "<extension-sdk> must specify an sdkVersion >= 0"); + } + if (minVersion < 0) { + return parseInput.error( + PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, + "<extension-sdk> must specify minExtensionVersion >= 0"); + } + + try { + int version = SdkExtensions.getExtensionVersion(sdkVersion); + if (version < minVersion) { + return parseInput.error( + PackageManager.INSTALL_FAILED_OLDER_SDK, + "Package requires " + sdkVersion + " extension version " + minVersion + + " which exceeds device version " + version); + } + } catch (RuntimeException e) { + return parseInput.error( + PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, + "Specified sdkVersion " + sdkVersion + " is not valid"); + } return parseInput.success(parsingPackage); } diff --git a/core/java/android/content/pm/parsing/ComponentParseUtils.java b/core/java/android/content/pm/parsing/ComponentParseUtils.java index f04a30ce4239..56ace5ecfa18 100644 --- a/core/java/android/content/pm/parsing/ComponentParseUtils.java +++ b/core/java/android/content/pm/parsing/ComponentParseUtils.java @@ -58,6 +58,7 @@ import android.util.TypedValue; import android.view.Gravity; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DataClass; import com.android.internal.util.XmlUtils; @@ -814,6 +815,11 @@ public class ComponentParseUtils { return exported; } + @VisibleForTesting + public void setExported(boolean exported) { + this.exported = exported; + } + public List<ParsedProviderIntentInfo> getIntents() { return intents; } diff --git a/core/java/android/hardware/biometrics/BiometricAuthenticator.java b/core/java/android/hardware/biometrics/BiometricAuthenticator.java index 698876b9c59e..11cf2d698730 100644 --- a/core/java/android/hardware/biometrics/BiometricAuthenticator.java +++ b/core/java/android/hardware/biometrics/BiometricAuthenticator.java @@ -18,6 +18,7 @@ package android.hardware.biometrics; import android.annotation.CallbackExecutor; import android.annotation.NonNull; +import android.hardware.biometrics.BiometricPrompt.AuthenticationResultType; import android.os.CancellationSignal; import android.os.Parcelable; @@ -119,6 +120,7 @@ public interface BiometricAuthenticator { class AuthenticationResult { private Identifier mIdentifier; private CryptoObject mCryptoObject; + private @AuthenticationResultType int mAuthenticationType; private int mUserId; /** @@ -129,27 +131,41 @@ public interface BiometricAuthenticator { /** * Authentication result * @param crypto + * @param authenticationType * @param identifier * @param userId * @hide */ - public AuthenticationResult(CryptoObject crypto, Identifier identifier, + public AuthenticationResult(CryptoObject crypto, + @AuthenticationResultType int authenticationType, Identifier identifier, int userId) { mCryptoObject = crypto; + mAuthenticationType = authenticationType; mIdentifier = identifier; mUserId = userId; } /** - * Obtain the crypto object associated with this transaction - * @return crypto object provided to {@link BiometricAuthenticator#authenticate( - * CryptoObject, CancellationSignal, Executor, AuthenticationCallback)} + * Provides the crypto object associated with this transaction. + * @return The crypto object provided to {@link BiometricPrompt#authenticate( + * BiometricPrompt.CryptoObject, CancellationSignal, Executor, + * BiometricPrompt.AuthenticationCallback)} */ public CryptoObject getCryptoObject() { return mCryptoObject; } /** + * Provides the type of authentication (e.g. device credential or biometric) that was + * requested from and successfully provided by the user. + * + * @return An integer value representing the authentication method used. + */ + public @AuthenticationResultType int getAuthenticationType() { + return mAuthenticationType; + } + + /** * Obtain the biometric identifier associated with this operation. Applications are strongly * discouraged from associating specific identifiers with specific applications or * operations. diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index cb8fc8b1cbb1..a695ce8e511f 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -21,6 +21,7 @@ import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; import static android.hardware.biometrics.BiometricManager.Authenticators; import android.annotation.CallbackExecutor; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -40,6 +41,8 @@ import android.util.Log; import com.android.internal.R; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.security.Signature; import java.util.concurrent.Executor; @@ -397,9 +400,11 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan new IBiometricServiceReceiver.Stub() { @Override - public void onAuthenticationSucceeded() throws RemoteException { + public void onAuthenticationSucceeded(@AuthenticationResultType int authenticationType) + throws RemoteException { mExecutor.execute(() -> { - final AuthenticationResult result = new AuthenticationResult(mCryptoObject); + final AuthenticationResult result = + new AuthenticationResult(mCryptoObject, authenticationType); mAuthenticationCallback.onAuthenticationSucceeded(result); }); } @@ -576,28 +581,62 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan } /** - * Container for callback data from {@link #authenticate( CancellationSignal, Executor, + * Authentication type reported by {@link AuthenticationResult} when the user authenticated by + * entering their device PIN, pattern, or password. + */ + public static final int AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL = 1; + + /** + * Authentication type reported by {@link AuthenticationResult} when the user authenticated by + * presenting some form of biometric (e.g. fingerprint or face). + */ + public static final int AUTHENTICATION_RESULT_TYPE_BIOMETRIC = 2; + + /** + * An {@link IntDef} representing the type of auth, as reported by {@link AuthenticationResult}. + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL, AUTHENTICATION_RESULT_TYPE_BIOMETRIC}) + public @interface AuthenticationResultType { + } + + /** + * Container for callback data from {@link #authenticate(CancellationSignal, Executor, * AuthenticationCallback)} and {@link #authenticate(CryptoObject, CancellationSignal, Executor, - * AuthenticationCallback)} + * AuthenticationCallback)}. */ public static class AuthenticationResult extends BiometricAuthenticator.AuthenticationResult { /** * Authentication result * @param crypto + * @param authenticationType * @hide */ - public AuthenticationResult(CryptoObject crypto) { + public AuthenticationResult(CryptoObject crypto, + @AuthenticationResultType int authenticationType) { // Identifier and userId is not used for BiometricPrompt. - super(crypto, null /* identifier */, 0 /* userId */); + super(crypto, authenticationType, null /* identifier */, 0 /* userId */); } + /** - * Obtain the crypto object associated with this transaction - * @return crypto object provided to {@link #authenticate( CryptoObject, CancellationSignal, - * Executor, AuthenticationCallback)} + * Provides the crypto object associated with this transaction. + * @return The crypto object provided to {@link #authenticate(CryptoObject, + * CancellationSignal, Executor, AuthenticationCallback)} */ public CryptoObject getCryptoObject() { return (CryptoObject) super.getCryptoObject(); } + + /** + * Provides the type of authentication (e.g. device credential or biometric) that was + * requested from and successfully provided by the user. + * + * @return An integer value representing the authentication method used. + */ + public @AuthenticationResultType int getAuthenticationType() { + return super.getAuthenticationType(); + } } /** diff --git a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl index c960049438f1..1d43aa640b40 100644 --- a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl +++ b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl @@ -20,8 +20,8 @@ package android.hardware.biometrics; * @hide */ oneway interface IBiometricServiceReceiver { - // Notify BiometricPrompt that authentication was successful - void onAuthenticationSucceeded(); + // Notify BiometricPrompt that authentication was successful. + void onAuthenticationSucceeded(int authenticationType); // Noties that authentication failed. void onAuthenticationFailed(); // Notify BiometricPrompt that an error has occurred. diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index a45648f06093..7bddc1decae1 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -1539,10 +1539,15 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p><code>p' = Rp</code></p> * <p>where <code>p</code> is in the device sensor coordinate system, and * <code>p'</code> is in the camera-oriented coordinate system.</p> + * <p>If {@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference} is UNDEFINED, the quaternion rotation cannot + * be accurately represented by the camera device, and will be represented by + * default values matching its default facing.</p> * <p><b>Units</b>: * Quaternion coefficients</p> * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> * <p><b>Permission {@link android.Manifest.permission#CAMERA } is needed to access this property</b></p> + * + * @see CameraCharacteristics#LENS_POSE_REFERENCE */ @PublicKey @NonNull @@ -1577,6 +1582,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>When {@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference} is GYROSCOPE, then this position is relative to * the center of the primary gyroscope on the device. The axis definitions are the same as * with PRIMARY_CAMERA.</p> + * <p>When {@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference} is UNDEFINED, this position cannot be accurately + * represented by the camera device, and will be represented as <code>(0, 0, 0)</code>.</p> * <p><b>Units</b>: Meters</p> * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> * <p><b>Permission {@link android.Manifest.permission#CAMERA } is needed to access this property</b></p> @@ -1714,20 +1721,24 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<float[]>("android.lens.radialDistortion", float[].class); /** - * <p>The origin for {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}.</p> + * <p>The origin for {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}, and the accuracy of + * {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation} and {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}.</p> * <p>Different calibration methods and use cases can produce better or worse results * depending on the selected coordinate origin.</p> * <p><b>Possible values:</b> * <ul> * <li>{@link #LENS_POSE_REFERENCE_PRIMARY_CAMERA PRIMARY_CAMERA}</li> * <li>{@link #LENS_POSE_REFERENCE_GYROSCOPE GYROSCOPE}</li> + * <li>{@link #LENS_POSE_REFERENCE_UNDEFINED UNDEFINED}</li> * </ul></p> * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> * <p><b>Permission {@link android.Manifest.permission#CAMERA } is needed to access this property</b></p> * + * @see CameraCharacteristics#LENS_POSE_ROTATION * @see CameraCharacteristics#LENS_POSE_TRANSLATION * @see #LENS_POSE_REFERENCE_PRIMARY_CAMERA * @see #LENS_POSE_REFERENCE_GYROSCOPE + * @see #LENS_POSE_REFERENCE_UNDEFINED */ @PublicKey @NonNull diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 8e0a46d52dd6..2377ccde5f89 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -366,6 +366,20 @@ public abstract class CameraMetadata<TKey> { */ public static final int LENS_POSE_REFERENCE_GYROSCOPE = 1; + /** + * <p>The camera device cannot represent the values of {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation} + * and {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation} accurately enough. One such example is a camera device + * on the cover of a foldable phone: in order to measure the pose translation and rotation, + * some kind of hinge position sensor would be needed.</p> + * <p>The value of {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation} must be all zeros, and + * {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation} must be values matching its default facing.</p> + * + * @see CameraCharacteristics#LENS_POSE_ROTATION + * @see CameraCharacteristics#LENS_POSE_TRANSLATION + * @see CameraCharacteristics#LENS_POSE_REFERENCE + */ + public static final int LENS_POSE_REFERENCE_UNDEFINED = 2; + // // Enumeration values for CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES // @@ -1121,12 +1135,16 @@ public abstract class CameraMetadata<TKey> { // /** - * <p>Timestamps from {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp} are in nanoseconds and monotonic, - * but can not be compared to timestamps from other subsystems - * (e.g. accelerometer, gyro etc.), or other instances of the same or different - * camera devices in the same system. Timestamps between streams and results for - * a single camera instance are comparable, and the timestamps for all buffers - * and the result metadata generated by a single capture are identical.</p> + * <p>Timestamps from {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp} are in nanoseconds and monotonic, but can + * not be compared to timestamps from other subsystems (e.g. accelerometer, gyro etc.), + * or other instances of the same or different camera devices in the same system with + * accuracy. However, the timestamps are roughly in the same timebase as + * {@link android.os.SystemClock#uptimeMillis }. The accuracy is sufficient for tasks + * like A/V synchronization for video recording, at least, and the timestamps can be + * directly used together with timestamps from the audio subsystem for that task.</p> + * <p>Timestamps between streams and results for a single camera instance are comparable, + * and the timestamps for all buffers and the result metadata generated by a single + * capture are identical.</p> * * @see CaptureResult#SENSOR_TIMESTAMP * @see CameraCharacteristics#SENSOR_INFO_TIMESTAMP_SOURCE @@ -1137,6 +1155,14 @@ public abstract class CameraMetadata<TKey> { * <p>Timestamps from {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp} are in the same timebase as * {@link android.os.SystemClock#elapsedRealtimeNanos }, * and they can be compared to other timestamps using that base.</p> + * <p>When buffers from a REALTIME device are passed directly to a video encoder from the + * camera, automatic compensation is done to account for differing timebases of the + * audio and camera subsystems. If the application is receiving buffers and then later + * sending them to a video encoder or other application where they are compared with + * audio subsystem timestamps or similar, this compensation is not present. In those + * cases, applications need to adjust the timestamps themselves. Since {@link android.os.SystemClock#elapsedRealtimeNanos } and {@link android.os.SystemClock#uptimeMillis } only diverge while the device is asleep, an + * offset between the two sources can be measured once per active session and applied + * to timestamps for sufficient accuracy for A/V sync.</p> * * @see CaptureResult#SENSOR_TIMESTAMP * @see CameraCharacteristics#SENSOR_INFO_TIMESTAMP_SOURCE diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index 9b305b32b61d..6f0d1358a1b7 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -3027,10 +3027,15 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p><code>p' = Rp</code></p> * <p>where <code>p</code> is in the device sensor coordinate system, and * <code>p'</code> is in the camera-oriented coordinate system.</p> + * <p>If {@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference} is UNDEFINED, the quaternion rotation cannot + * be accurately represented by the camera device, and will be represented by + * default values matching its default facing.</p> * <p><b>Units</b>: * Quaternion coefficients</p> * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> * <p><b>Permission {@link android.Manifest.permission#CAMERA } is needed to access this property</b></p> + * + * @see CameraCharacteristics#LENS_POSE_REFERENCE */ @PublicKey @NonNull @@ -3065,6 +3070,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p>When {@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference} is GYROSCOPE, then this position is relative to * the center of the primary gyroscope on the device. The axis definitions are the same as * with PRIMARY_CAMERA.</p> + * <p>When {@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference} is UNDEFINED, this position cannot be accurately + * represented by the camera device, and will be represented as <code>(0, 0, 0)</code>.</p> * <p><b>Units</b>: Meters</p> * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> * <p><b>Permission {@link android.Manifest.permission#CAMERA } is needed to access this property</b></p> diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java index 60e466e5f278..1932f46975c5 100644 --- a/core/java/android/hardware/soundtrigger/SoundTrigger.java +++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java @@ -34,6 +34,7 @@ import android.content.Context; import android.media.AudioFormat; import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService; import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor; +import android.media.soundtrigger_middleware.Status; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -41,6 +42,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.ServiceSpecificException; import android.util.Log; import java.lang.annotation.Retention; @@ -303,16 +305,20 @@ public class SoundTrigger { @NonNull public final UUID vendorUuid; + /** vendor specific version number of the model */ + public final int version; + /** Opaque data. For use by vendor implementation and enrollment application */ @UnsupportedAppUsage @NonNull public final byte[] data; public SoundModel(@NonNull UUID uuid, @Nullable UUID vendorUuid, int type, - @Nullable byte[] data) { + @Nullable byte[] data, int version) { this.uuid = requireNonNull(uuid); this.vendorUuid = vendorUuid != null ? vendorUuid : new UUID(0, 0); this.type = type; + this.version = version; this.data = data != null ? data : new byte[0]; } @@ -320,6 +326,7 @@ public class SoundTrigger { public int hashCode() { final int prime = 31; int result = 1; + result = prime * result + version; result = prime * result + Arrays.hashCode(data); result = prime * result + type; result = prime * result + ((uuid == null) ? 0 : uuid.hashCode()); @@ -350,6 +357,8 @@ public class SoundTrigger { return false; if (!Arrays.equals(data, other.data)) return false; + if (version != other.version) + return false; return true; } } @@ -499,14 +508,19 @@ public class SoundTrigger { @NonNull public final Keyphrase[] keyphrases; // keyword phrases in model - @UnsupportedAppUsage public KeyphraseSoundModel( @NonNull UUID uuid, @NonNull UUID vendorUuid, @Nullable byte[] data, - @Nullable Keyphrase[] keyphrases) { - super(uuid, vendorUuid, TYPE_KEYPHRASE, data); + @Nullable Keyphrase[] keyphrases, int version) { + super(uuid, vendorUuid, TYPE_KEYPHRASE, data, version); this.keyphrases = keyphrases != null ? keyphrases : new Keyphrase[0]; } + @UnsupportedAppUsage + public KeyphraseSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid, + @Nullable byte[] data, @Nullable Keyphrase[] keyphrases) { + this(uuid, vendorUuid, data, keyphrases, -1); + } + public static final @android.annotation.NonNull Parcelable.Creator<KeyphraseSoundModel> CREATOR = new Parcelable.Creator<KeyphraseSoundModel>() { public KeyphraseSoundModel createFromParcel(Parcel in) { @@ -525,9 +539,10 @@ public class SoundTrigger { if (length >= 0) { vendorUuid = UUID.fromString(in.readString()); } + int version = in.readInt(); byte[] data = in.readBlob(); Keyphrase[] keyphrases = in.createTypedArray(Keyphrase.CREATOR); - return new KeyphraseSoundModel(uuid, vendorUuid, data, keyphrases); + return new KeyphraseSoundModel(uuid, vendorUuid, data, keyphrases, version); } @Override @@ -544,6 +559,7 @@ public class SoundTrigger { dest.writeInt(vendorUuid.toString().length()); dest.writeString(vendorUuid.toString()); } + dest.writeInt(version); dest.writeBlob(data); dest.writeTypedArray(keyphrases, flags); } @@ -552,7 +568,9 @@ public class SoundTrigger { public String toString() { return "KeyphraseSoundModel [keyphrases=" + Arrays.toString(keyphrases) + ", uuid=" + uuid + ", vendorUuid=" + vendorUuid - + ", type=" + type + ", data=" + (data == null ? 0 : data.length) + "]"; + + ", type=" + type + + ", data=" + (data == null ? 0 : data.length) + + ", version=" + version + "]"; } @Override @@ -598,10 +616,15 @@ public class SoundTrigger { } }; + public GenericSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid, + @Nullable byte[] data, int version) { + super(uuid, vendorUuid, TYPE_GENERIC_SOUND, data, version); + } + @UnsupportedAppUsage public GenericSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid, @Nullable byte[] data) { - super(uuid, vendorUuid, TYPE_GENERIC_SOUND, data); + this(uuid, vendorUuid, data, -1); } @Override @@ -617,7 +640,8 @@ public class SoundTrigger { vendorUuid = UUID.fromString(in.readString()); } byte[] data = in.readBlob(); - return new GenericSoundModel(uuid, vendorUuid, data); + int version = in.readInt(); + return new GenericSoundModel(uuid, vendorUuid, data, version); } @Override @@ -630,12 +654,15 @@ public class SoundTrigger { dest.writeString(vendorUuid.toString()); } dest.writeBlob(data); + dest.writeInt(version); } @Override public String toString() { return "GenericSoundModel [uuid=" + uuid + ", vendorUuid=" + vendorUuid - + ", type=" + type + ", data=" + (data == null ? 0 : data.length) + "]"; + + ", type=" + type + + ", data=" + (data == null ? 0 : data.length) + + ", version=" + version + "]"; } } @@ -1651,6 +1678,45 @@ public class SoundTrigger { } /** + * Translate an exception thrown from interaction with the underlying service to an error code. + * Throws a runtime exception for unexpected conditions. + * @param e The caught exception. + * @return The error code. + * + * @hide + */ + static int handleException(Exception e) { + Log.w(TAG, "Exception caught", e); + if (e instanceof RemoteException) { + return STATUS_DEAD_OBJECT; + } + if (e instanceof ServiceSpecificException) { + switch (((ServiceSpecificException) e).errorCode) { + case Status.OPERATION_NOT_SUPPORTED: + return STATUS_INVALID_OPERATION; + case Status.TEMPORARY_PERMISSION_DENIED: + return STATUS_PERMISSION_DENIED; + case Status.DEAD_OBJECT: + return STATUS_DEAD_OBJECT; + } + return STATUS_ERROR; + } + if (e instanceof SecurityException) { + return STATUS_PERMISSION_DENIED; + } + if (e instanceof IllegalStateException) { + return STATUS_INVALID_OPERATION; + } + if (e instanceof IllegalArgumentException || e instanceof NullPointerException) { + return STATUS_BAD_VALUE; + } + // This is not one of the conditions represented by our error code, escalate to a + // RuntimeException. + Log.e(TAG, "Escalating unexpected exception: ", e); + throw new RuntimeException(e); + } + + /** * Returns a list of descriptors for all hardware modules loaded. * @param modules A ModuleProperties array where the list will be returned. * @return - {@link #STATUS_OK} in case of success @@ -1672,9 +1738,8 @@ public class SoundTrigger { modules.add(ConversionUtil.aidl2apiModuleDescriptor(desc)); } return STATUS_OK; - } catch (RemoteException e) { - Log.e(TAG, "Exception caught", e); - return STATUS_DEAD_OBJECT; + } catch (Exception e) { + return handleException(e); } } diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java index 72914192d73c..9bd39925f83f 100644 --- a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java +++ b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java @@ -78,7 +78,7 @@ public class SoundTriggerModule { mService = null; } } catch (Exception e) { - handleException(e); + SoundTrigger.handleException(e); } } @@ -115,7 +115,7 @@ public class SoundTriggerModule { } return SoundTrigger.STATUS_BAD_VALUE; } catch (Exception e) { - return handleException(e); + return SoundTrigger.handleException(e); } } @@ -137,7 +137,7 @@ public class SoundTriggerModule { mService.unloadModel(soundModelHandle); return SoundTrigger.STATUS_OK; } catch (Exception e) { - return handleException(e); + return SoundTrigger.handleException(e); } } @@ -166,7 +166,7 @@ public class SoundTriggerModule { ConversionUtil.api2aidlRecognitionConfig(config)); return SoundTrigger.STATUS_OK; } catch (Exception e) { - return handleException(e); + return SoundTrigger.handleException(e); } } @@ -189,7 +189,7 @@ public class SoundTriggerModule { mService.stopRecognition(soundModelHandle); return SoundTrigger.STATUS_OK; } catch (Exception e) { - return handleException(e); + return SoundTrigger.handleException(e); } } @@ -214,7 +214,7 @@ public class SoundTriggerModule { mService.forceRecognitionEvent(soundModelHandle); return SoundTrigger.STATUS_OK; } catch (Exception e) { - return handleException(e); + return SoundTrigger.handleException(e); } } @@ -242,7 +242,7 @@ public class SoundTriggerModule { ConversionUtil.api2aidlModelParameter(modelParam), value); return SoundTrigger.STATUS_OK; } catch (Exception e) { - return handleException(e); + return SoundTrigger.handleException(e); } } @@ -296,23 +296,6 @@ public class SoundTriggerModule { } } - private int handleException(Exception e) { - Log.e(TAG, "", e); - if (e instanceof NullPointerException) { - return SoundTrigger.STATUS_NO_INIT; - } - if (e instanceof RemoteException) { - return SoundTrigger.STATUS_DEAD_OBJECT; - } - if (e instanceof IllegalArgumentException) { - return SoundTrigger.STATUS_BAD_VALUE; - } - if (e instanceof IllegalStateException) { - return SoundTrigger.STATUS_INVALID_OPERATION; - } - return SoundTrigger.STATUS_ERROR; - } - private class EventHandlerDelegate extends ISoundTriggerCallback.Stub implements IBinder.DeathRecipient { private final Handler mHandler; @@ -370,6 +353,12 @@ public class SoundTriggerModule { } @Override + public synchronized void onModuleDied() { + Message m = mHandler.obtainMessage(EVENT_SERVICE_DIED); + mHandler.sendMessage(m); + } + + @Override public synchronized void binderDied() { Message m = mHandler.obtainMessage(EVENT_SERVICE_DIED); mHandler.sendMessage(m); diff --git a/core/java/android/net/CaptivePortal.java b/core/java/android/net/CaptivePortal.java index a66fcae7d4a2..fb35b4bde303 100644 --- a/core/java/android/net/CaptivePortal.java +++ b/core/java/android/net/CaptivePortal.java @@ -60,6 +60,18 @@ public class CaptivePortal implements Parcelable { @SystemApi @TestApi public static final int APP_RETURN_WANTED_AS_IS = 2; + /** Event offset of request codes from captive portal application. */ + private static final int APP_REQUEST_BASE = 100; + /** + * Request code from the captive portal application, indicating that the network condition may + * have changed and the network should be re-validated. + * @see ICaptivePortal#appRequest(int) + * @see android.net.INetworkMonitor#forceReevaluation(int) + * @hide + */ + @SystemApi + @TestApi + public static final int APP_REQUEST_REEVALUATION_REQUIRED = APP_REQUEST_BASE + 0; private final IBinder mBinder; @@ -136,6 +148,19 @@ public class CaptivePortal implements Parcelable { } /** + * Request that the system reevaluates the captive portal status. + * @hide + */ + @SystemApi + @TestApi + public void reevaluateNetwork() { + try { + ICaptivePortal.Stub.asInterface(mBinder).appRequest(APP_REQUEST_REEVALUATION_REQUIRED); + } catch (RemoteException e) { + } + } + + /** * Log a captive portal login event. * @param eventId one of the CAPTIVE_PORTAL_LOGIN_* constants in metrics_constants.proto. * @param packageName captive portal application package name. diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 4bf3e905697d..8ba3131a83f1 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -517,7 +517,7 @@ public class ConnectivityManager { * The absence of a connection type. * @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) + @SystemApi public static final int TYPE_NONE = -1; /** @@ -662,7 +662,7 @@ public class ConnectivityManager { * {@hide} */ @Deprecated - @UnsupportedAppUsage + @SystemApi public static final int TYPE_WIFI_P2P = 13; /** @@ -3169,10 +3169,10 @@ public class ConnectivityManager { /** * @hide * Register a NetworkAgent with ConnectivityService. - * @return NetID corresponding to NetworkAgent. + * @return Network corresponding to NetworkAgent. */ @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) - public int registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp, + public Network registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp, NetworkCapabilities nc, int score, NetworkAgentConfig config) { return registerNetworkAgent(messenger, ni, lp, nc, score, config, NetworkProvider.ID_NONE); } @@ -3180,10 +3180,10 @@ public class ConnectivityManager { /** * @hide * Register a NetworkAgent with ConnectivityService. - * @return NetID corresponding to NetworkAgent. + * @return Network corresponding to NetworkAgent. */ @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) - public int registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp, + public Network registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp, NetworkCapabilities nc, int score, NetworkAgentConfig config, int providerId) { try { return mService.registerNetworkAgent(messenger, ni, lp, nc, score, config, providerId); @@ -3622,14 +3622,26 @@ public class ConnectivityManager { /** * Helper function to request a network with a particular legacy type. * - * This is temporarily public @hide so it can be called by system code that uses the - * NetworkRequest API to request networks but relies on CONNECTIVITY_ACTION broadcasts for - * instead network notifications. + * @deprecated This is temporarily public for tethering to backwards compatibility that uses + * the NetworkRequest API to request networks with legacy type and relies on + * CONNECTIVITY_ACTION broadcasts instead of NetworkCallbacks. New caller should use + * {@link #requestNetwork(NetworkRequest, NetworkCallback, Handler)} instead. * * TODO: update said system code to rely on NetworkCallbacks and make this method private. + + * @param request {@link NetworkRequest} describing this request. + * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note + * the callback must not be shared - it uniquely specifies this request. + * @param timeoutMs The time in milliseconds to attempt looking for a suitable network + * before {@link NetworkCallback#onUnavailable()} is called. The timeout must + * be a positive value (i.e. >0). + * @param legacyType to specify the network type(#TYPE_*). + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. * * @hide */ + @SystemApi + @Deprecated public void requestNetwork(@NonNull NetworkRequest request, @NonNull NetworkCallback networkCallback, int timeoutMs, int legacyType, @NonNull Handler handler) { diff --git a/core/java/android/net/ICaptivePortal.aidl b/core/java/android/net/ICaptivePortal.aidl index 707b4f699873..fe21905c7002 100644 --- a/core/java/android/net/ICaptivePortal.aidl +++ b/core/java/android/net/ICaptivePortal.aidl @@ -21,6 +21,7 @@ package android.net; * @hide */ oneway interface ICaptivePortal { + void appRequest(int request); void appResponse(int response); void logEvent(int eventId, String packageName); } diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 7691beba3208..186196bd31c7 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -152,7 +152,7 @@ interface IConnectivityManager void declareNetworkRequestUnfulfillable(in NetworkRequest request); - int registerNetworkAgent(in Messenger messenger, in NetworkInfo ni, in LinkProperties lp, + Network registerNetworkAgent(in Messenger messenger, in NetworkInfo ni, in LinkProperties lp, in NetworkCapabilities nc, int score, in NetworkAgentConfig config, in int factorySerialNumber); diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl index 385cb1d68b57..72a6b397a30c 100644 --- a/core/java/android/net/INetworkPolicyManager.aidl +++ b/core/java/android/net/INetworkPolicyManager.aidl @@ -57,9 +57,6 @@ interface INetworkPolicyManager { @UnsupportedAppUsage boolean getRestrictBackground(); - /** Callback used to change internal state on tethering */ - void onTetheringChanged(String iface, boolean tethering); - /** Gets the restrict background status based on the caller's UID: 1 - disabled 2 - whitelisted diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java index 2792c564988a..be8e561fd044 100644 --- a/core/java/android/net/LinkProperties.java +++ b/core/java/android/net/LinkProperties.java @@ -21,6 +21,8 @@ import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; +import android.net.util.LinkPropertiesUtils; +import android.net.util.LinkPropertiesUtils.CompareResult; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; @@ -84,36 +86,6 @@ public final class LinkProperties implements Parcelable { /** * @hide */ - public static class CompareResult<T> { - public final List<T> removed = new ArrayList<>(); - public final List<T> added = new ArrayList<>(); - - public CompareResult() {} - - public CompareResult(Collection<T> oldItems, Collection<T> newItems) { - if (oldItems != null) { - removed.addAll(oldItems); - } - if (newItems != null) { - for (T newItem : newItems) { - if (!removed.remove(newItem)) { - added.add(newItem); - } - } - } - } - - @Override - public String toString() { - return "removed=[" + TextUtils.join(",", removed) - + "] added=[" + TextUtils.join(",", added) - + "]"; - } - } - - /** - * @hide - */ @UnsupportedAppUsage(implicitMember = "values()[Landroid/net/LinkProperties$ProvisioningChange;") public enum ProvisioningChange { @@ -1295,7 +1267,7 @@ public final class LinkProperties implements Parcelable { */ @UnsupportedAppUsage public boolean isIdenticalInterfaceName(@NonNull LinkProperties target) { - return TextUtils.equals(getInterfaceName(), target.getInterfaceName()); + return LinkPropertiesUtils.isIdenticalInterfaceName(target, this); } /** @@ -1318,10 +1290,7 @@ public final class LinkProperties implements Parcelable { */ @UnsupportedAppUsage public boolean isIdenticalAddresses(@NonNull LinkProperties target) { - Collection<InetAddress> targetAddresses = target.getAddresses(); - Collection<InetAddress> sourceAddresses = getAddresses(); - return (sourceAddresses.size() == targetAddresses.size()) ? - sourceAddresses.containsAll(targetAddresses) : false; + return LinkPropertiesUtils.isIdenticalAddresses(target, this); } /** @@ -1333,15 +1302,7 @@ public final class LinkProperties implements Parcelable { */ @UnsupportedAppUsage public boolean isIdenticalDnses(@NonNull LinkProperties target) { - Collection<InetAddress> targetDnses = target.getDnsServers(); - String targetDomains = target.getDomains(); - if (mDomains == null) { - if (targetDomains != null) return false; - } else { - if (!mDomains.equals(targetDomains)) return false; - } - return (mDnses.size() == targetDnses.size()) ? - mDnses.containsAll(targetDnses) : false; + return LinkPropertiesUtils.isIdenticalDnses(target, this); } /** @@ -1394,9 +1355,7 @@ public final class LinkProperties implements Parcelable { */ @UnsupportedAppUsage public boolean isIdenticalRoutes(@NonNull LinkProperties target) { - Collection<RouteInfo> targetRoutes = target.getRoutes(); - return (mRoutes.size() == targetRoutes.size()) ? - mRoutes.containsAll(targetRoutes) : false; + return LinkPropertiesUtils.isIdenticalRoutes(target, this); } /** @@ -1408,8 +1367,7 @@ public final class LinkProperties implements Parcelable { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public boolean isIdenticalHttpProxy(@NonNull LinkProperties target) { - return getHttpProxy() == null ? target.getHttpProxy() == null : - getHttpProxy().equals(target.getHttpProxy()); + return LinkPropertiesUtils.isIdenticalHttpProxy(target, this); } /** @@ -1541,26 +1499,6 @@ public final class LinkProperties implements Parcelable { } /** - * Compares the addresses in this LinkProperties with another - * LinkProperties, examining only addresses on the base link. - * - * @param target a LinkProperties with the new list of addresses - * @return the differences between the addresses. - * @hide - */ - public @NonNull CompareResult<LinkAddress> compareAddresses(@Nullable LinkProperties target) { - /* - * Duplicate the LinkAddresses into removed, we will be removing - * address which are common between mLinkAddresses and target - * leaving the addresses that are different. And address which - * are in target but not in mLinkAddresses are placed in the - * addedAddresses. - */ - return new CompareResult<>(mLinkAddresses, - target != null ? target.getLinkAddresses() : null); - } - - /** * Compares the DNS addresses in this LinkProperties with another * LinkProperties, examining only DNS addresses on the base link. * diff --git a/core/java/android/net/MacAddress.java b/core/java/android/net/MacAddress.java index 74c9aac05b41..0e10c42e61db 100644 --- a/core/java/android/net/MacAddress.java +++ b/core/java/android/net/MacAddress.java @@ -20,11 +20,11 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; +import android.net.util.MacAddressUtils; import android.net.wifi.WifiInfo; import android.os.Parcel; import android.os.Parcelable; -import com.android.internal.util.BitUtils; import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; @@ -33,7 +33,6 @@ import java.net.Inet6Address; import java.net.UnknownHostException; import java.security.SecureRandom; import java.util.Arrays; -import java.util.Random; /** * Representation of a MAC address. @@ -109,21 +108,13 @@ public final class MacAddress implements Parcelable { if (equals(BROADCAST_ADDRESS)) { return TYPE_BROADCAST; } - if (isMulticastAddress()) { + if ((mAddr & MULTICAST_MASK) != 0) { return TYPE_MULTICAST; } return TYPE_UNICAST; } /** - * @return true if this MacAddress is a multicast address. - * @hide - */ - public boolean isMulticastAddress() { - return (mAddr & MULTICAST_MASK) != 0; - } - - /** * @return true if this MacAddress is a locally assigned address. */ public boolean isLocallyAssigned() { @@ -192,7 +183,7 @@ public final class MacAddress implements Parcelable { * @hide */ public static boolean isMacAddress(byte[] addr) { - return addr != null && addr.length == ETHER_ADDR_LEN; + return MacAddressUtils.isMacAddress(addr); } /** @@ -261,26 +252,11 @@ public final class MacAddress implements Parcelable { } private static byte[] byteAddrFromLongAddr(long addr) { - byte[] bytes = new byte[ETHER_ADDR_LEN]; - int index = ETHER_ADDR_LEN; - while (index-- > 0) { - bytes[index] = (byte) addr; - addr = addr >> 8; - } - return bytes; + return MacAddressUtils.byteAddrFromLongAddr(addr); } private static long longAddrFromByteAddr(byte[] addr) { - Preconditions.checkNotNull(addr); - if (!isMacAddress(addr)) { - throw new IllegalArgumentException( - Arrays.toString(addr) + " was not a valid MAC address"); - } - long longAddr = 0; - for (byte b : addr) { - longAddr = (longAddr << 8) + BitUtils.uint8(b); - } - return longAddr; + return MacAddressUtils.longAddrFromByteAddr(addr); } // Internal conversion function equivalent to longAddrFromByteAddr(byteAddrFromStringAddr(addr)) @@ -350,50 +326,7 @@ public final class MacAddress implements Parcelable { * @hide */ public static @NonNull MacAddress createRandomUnicastAddressWithGoogleBase() { - return createRandomUnicastAddress(BASE_GOOGLE_MAC, new SecureRandom()); - } - - /** - * Returns a generated MAC address whose 46 bits, excluding the locally assigned bit and the - * unicast bit, are randomly selected. - * - * The locally assigned bit is always set to 1. The multicast bit is always set to 0. - * - * @return a random locally assigned, unicast MacAddress. - * - * @hide - */ - public static @NonNull MacAddress createRandomUnicastAddress() { - return createRandomUnicastAddress(null, new SecureRandom()); - } - - /** - * Returns a randomly generated MAC address using the given Random object and the same - * OUI values as the given MacAddress. - * - * The locally assigned bit is always set to 1. The multicast bit is always set to 0. - * - * @param base a base MacAddress whose OUI is used for generating the random address. - * If base == null then the OUI will also be randomized. - * @param r a standard Java Random object used for generating the random address. - * @return a random locally assigned MacAddress. - * - * @hide - */ - public static @NonNull MacAddress createRandomUnicastAddress(MacAddress base, Random r) { - long addr; - if (base == null) { - addr = r.nextLong() & VALID_LONG_MASK; - } else { - addr = (base.mAddr & OUI_MASK) | (NIC_MASK & r.nextLong()); - } - addr |= LOCALLY_ASSIGNED_MASK; - addr &= ~MULTICAST_MASK; - MacAddress mac = new MacAddress(addr); - if (mac.equals(DEFAULT_MAC_ADDRESS)) { - return createRandomUnicastAddress(base, r); - } - return mac; + return MacAddressUtils.createRandomUnicastAddress(BASE_GOOGLE_MAC, new SecureRandom()); } // Convenience function for working around the lack of byte literals. diff --git a/core/java/android/net/NattKeepalivePacketData.java b/core/java/android/net/NattKeepalivePacketData.java index 3fb52f12a88d..bd39c13ba092 100644 --- a/core/java/android/net/NattKeepalivePacketData.java +++ b/core/java/android/net/NattKeepalivePacketData.java @@ -16,9 +16,11 @@ package android.net; -import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS; -import static android.net.SocketKeepalive.ERROR_INVALID_PORT; +import static android.net.InvalidPacketException.ERROR_INVALID_IP_ADDRESS; +import static android.net.InvalidPacketException.ERROR_INVALID_PORT; +import android.annotation.NonNull; +import android.annotation.SystemApi; import android.net.util.IpUtils; import android.os.Parcel; import android.os.Parcelable; @@ -30,20 +32,22 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; /** @hide */ +@SystemApi public final class NattKeepalivePacketData extends KeepalivePacketData implements Parcelable { private static final int IPV4_HEADER_LENGTH = 20; private static final int UDP_HEADER_LENGTH = 8; // This should only be constructed via static factory methods, such as // nattKeepalivePacket - private NattKeepalivePacketData(InetAddress srcAddress, int srcPort, - InetAddress dstAddress, int dstPort, byte[] data) throws + public NattKeepalivePacketData(@NonNull InetAddress srcAddress, int srcPort, + @NonNull InetAddress dstAddress, int dstPort, @NonNull byte[] data) throws InvalidPacketException { super(srcAddress, srcPort, dstAddress, dstPort, data); } /** * Factory method to create Nat-T keepalive packet structure. + * @hide */ public static NattKeepalivePacketData nattKeepalivePacket( InetAddress srcAddress, int srcPort, InetAddress dstAddress, int dstPort) @@ -87,7 +91,7 @@ public final class NattKeepalivePacketData extends KeepalivePacketData implement } /** Write to parcel */ - public void writeToParcel(Parcel out, int flags) { + public void writeToParcel(@NonNull Parcel out, int flags) { out.writeString(srcAddress.getHostAddress()); out.writeString(dstAddress.getHostAddress()); out.writeInt(srcPort); @@ -95,7 +99,7 @@ public final class NattKeepalivePacketData extends KeepalivePacketData implement } /** Parcelable Creator */ - public static final Parcelable.Creator<NattKeepalivePacketData> CREATOR = + public static final @NonNull Parcelable.Creator<NattKeepalivePacketData> CREATOR = new Parcelable.Creator<NattKeepalivePacketData>() { public NattKeepalivePacketData createFromParcel(Parcel in) { final InetAddress srcAddress = diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java index fc72eecd4145..aae9fd4725b4 100644 --- a/core/java/android/net/NetworkAgent.java +++ b/core/java/android/net/NetworkAgent.java @@ -17,6 +17,8 @@ package android.net; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.os.Build; @@ -43,10 +45,13 @@ import java.util.concurrent.atomic.AtomicBoolean; * * @hide */ +@SystemApi public abstract class NetworkAgent { - // Guaranteed to be valid (not NETID_UNSET), otherwise registerNetworkAgent() would have thrown - // an exception. - public final int netId; + /** + * The {@link Network} corresponding to this object. + */ + @NonNull + public final Network network; private final Handler mHandler; private volatile AsyncChannel mAsyncChannel; @@ -57,9 +62,14 @@ public abstract class NetworkAgent { private final ArrayList<Message>mPreConnectedQueue = new ArrayList<Message>(); private volatile long mLastBwRefreshTime = 0; private static final long BW_REFRESH_MIN_WIN_MS = 500; - private boolean mPollLceScheduled = false; - private AtomicBoolean mPollLcePending = new AtomicBoolean(false); - public final int mProviderId; + private boolean mBandwidthUpdateScheduled = false; + private AtomicBoolean mBandwidthUpdatePending = new AtomicBoolean(false); + + /** + * The ID of the {@link NetworkProvider} that created this object, or + * {@link NetworkProvider#ID_NONE} if unknown. + */ + public final int providerId; private static final int BASE = Protocol.BASE_NETWORK_AGENT; @@ -67,6 +77,7 @@ public abstract class NetworkAgent { * Sent by ConnectivityService to the NetworkAgent to inform it of * suspected connectivity problems on its network. The NetworkAgent * should take steps to verify and correct connectivity. + * @hide */ public static final int CMD_SUSPECT_BAD = BASE; @@ -75,6 +86,7 @@ public abstract class NetworkAgent { * ConnectivityService to pass the current NetworkInfo (connection state). * Sent when the NetworkInfo changes, mainly due to change of state. * obj = NetworkInfo + * @hide */ public static final int EVENT_NETWORK_INFO_CHANGED = BASE + 1; @@ -82,6 +94,7 @@ public abstract class NetworkAgent { * Sent by the NetworkAgent to ConnectivityService to pass the current * NetworkCapabilties. * obj = NetworkCapabilities + * @hide */ public static final int EVENT_NETWORK_CAPABILITIES_CHANGED = BASE + 2; @@ -89,11 +102,14 @@ public abstract class NetworkAgent { * Sent by the NetworkAgent to ConnectivityService to pass the current * NetworkProperties. * obj = NetworkProperties + * @hide */ public static final int EVENT_NETWORK_PROPERTIES_CHANGED = BASE + 3; - /* centralize place where base network score, and network score scaling, will be + /** + * Centralize the place where base network score, and network score scaling, will be * stored, so as we can consistently compare apple and oranges, or wifi, ethernet and LTE + * @hide */ public static final int WIFI_BASE_SCORE = 60; @@ -101,6 +117,7 @@ public abstract class NetworkAgent { * Sent by the NetworkAgent to ConnectivityService to pass the current * network score. * obj = network score Integer + * @hide */ public static final int EVENT_NETWORK_SCORE_CHANGED = BASE + 4; @@ -113,12 +130,33 @@ public abstract class NetworkAgent { * obj = Bundle containing map from {@code REDIRECT_URL_KEY} to {@code String} * representing URL that Internet probe was redirect to, if it was redirected, * or mapping to {@code null} otherwise. + * @hide */ public static final int CMD_REPORT_NETWORK_STATUS = BASE + 7; + + /** + * Network validation suceeded. + * Corresponds to {@link NetworkCapabilities.NET_CAPABILITY_VALIDATED}. + */ + public static final int VALIDATION_STATUS_VALID = 1; + + /** + * Network validation was attempted and failed. This may be received more than once as + * subsequent validation attempts are made. + */ + public static final int VALIDATION_STATUS_NOT_VALID = 2; + + // TODO: remove. + /** @hide */ public static final int VALID_NETWORK = 1; + /** @hide */ public static final int INVALID_NETWORK = 2; + /** + * The key for the redirect URL in the Bundle argument of {@code CMD_REPORT_NETWORK_STATUS}. + * @hide + */ public static String REDIRECT_URL_KEY = "redirect URL"; /** @@ -127,6 +165,7 @@ public abstract class NetworkAgent { * CONNECTED so it can be given special treatment at that time. * * obj = boolean indicating whether to use this network even if unvalidated + * @hide */ public static final int EVENT_SET_EXPLICITLY_SELECTED = BASE + 8; @@ -137,12 +176,14 @@ public abstract class NetworkAgent { * responsibility to remember it. * * arg1 = 1 if true, 0 if false + * @hide */ public static final int CMD_SAVE_ACCEPT_UNVALIDATED = BASE + 9; /** * Sent by ConnectivityService to the NetworkAgent to inform the agent to pull * the underlying network connection for updated bandwidth information. + * @hide */ public static final int CMD_REQUEST_BANDWIDTH_UPDATE = BASE + 10; @@ -155,6 +196,7 @@ public abstract class NetworkAgent { * obj = KeepalivePacketData object describing the data to be sent * * Also used internally by ConnectivityService / KeepaliveTracker, with different semantics. + * @hide */ public static final int CMD_START_SOCKET_KEEPALIVE = BASE + 11; @@ -164,6 +206,7 @@ public abstract class NetworkAgent { * arg1 = slot number of the keepalive to stop. * * Also used internally by ConnectivityService / KeepaliveTracker, with different semantics. + * @hide */ public static final int CMD_STOP_SOCKET_KEEPALIVE = BASE + 12; @@ -177,6 +220,7 @@ public abstract class NetworkAgent { * * arg1 = slot number of the keepalive * arg2 = error code + * @hide */ public static final int EVENT_SOCKET_KEEPALIVE = BASE + 13; @@ -185,6 +229,7 @@ public abstract class NetworkAgent { * that when crossed should trigger a system wakeup and a NetworkCapabilities update. * * obj = int[] describing signal strength thresholds. + * @hide */ public static final int CMD_SET_SIGNAL_STRENGTH_THRESHOLDS = BASE + 14; @@ -192,6 +237,7 @@ public abstract class NetworkAgent { * Sent by ConnectivityService to the NeworkAgent to inform the agent to avoid * automatically reconnecting to this network (e.g. via autojoin). Happens * when user selects "No" option on the "Stay connected?" dialog box. + * @hide */ public static final int CMD_PREVENT_AUTOMATIC_RECONNECT = BASE + 15; @@ -204,6 +250,7 @@ public abstract class NetworkAgent { * This does not happen with UDP, so this message is TCP-specific. * arg1 = slot number of the keepalive to filter for. * obj = the keepalive packet to send repeatedly. + * @hide */ public static final int CMD_ADD_KEEPALIVE_PACKET_FILTER = BASE + 16; @@ -211,6 +258,7 @@ public abstract class NetworkAgent { * Sent by the KeepaliveTracker to NetworkAgent to remove a packet filter. See * {@link #CMD_ADD_KEEPALIVE_PACKET_FILTER}. * arg1 = slot number of the keepalive packet filter to remove. + * @hide */ public static final int CMD_REMOVE_KEEPALIVE_PACKET_FILTER = BASE + 17; @@ -218,27 +266,32 @@ public abstract class NetworkAgent { // of dependent changes that would conflict throughout the automerger graph. Having these // temporarily helps with the process of going through with all these dependent changes across // the entire tree. + /** @hide TODO: decide which of these to expose. */ public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni, NetworkCapabilities nc, LinkProperties lp, int score) { this(looper, context, logTag, ni, nc, lp, score, null, NetworkProvider.ID_NONE); } + + /** @hide TODO: decide which of these to expose. */ public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni, NetworkCapabilities nc, LinkProperties lp, int score, NetworkAgentConfig config) { this(looper, context, logTag, ni, nc, lp, score, config, NetworkProvider.ID_NONE); } + /** @hide TODO: decide which of these to expose. */ public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni, NetworkCapabilities nc, LinkProperties lp, int score, int providerId) { this(looper, context, logTag, ni, nc, lp, score, null, providerId); } + /** @hide TODO: decide which of these to expose. */ public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni, NetworkCapabilities nc, LinkProperties lp, int score, NetworkAgentConfig config, int providerId) { mHandler = new NetworkAgentHandler(looper); LOG_TAG = logTag; mContext = context; - mProviderId = providerId; + this.providerId = providerId; if (ni == null || nc == null || lp == null) { throw new IllegalArgumentException(); } @@ -246,7 +299,7 @@ public abstract class NetworkAgent { if (VDBG) log("Registering NetworkAgent"); ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService( Context.CONNECTIVITY_SERVICE); - netId = cm.registerNetworkAgent(new Messenger(mHandler), new NetworkInfo(ni), + network = cm.registerNetworkAgent(new Messenger(mHandler), new NetworkInfo(ni), new LinkProperties(lp), new NetworkCapabilities(nc), score, config, providerId); } @@ -286,7 +339,7 @@ public abstract class NetworkAgent { case AsyncChannel.CMD_CHANNEL_DISCONNECTED: { if (DBG) log("NetworkAgent channel lost"); // let the client know CS is done with us. - unwanted(); + onNetworkUnwanted(); synchronized (mPreConnectedQueue) { mAsyncChannel = null; } @@ -302,16 +355,16 @@ public abstract class NetworkAgent { log("CMD_REQUEST_BANDWIDTH_UPDATE request received."); } if (currentTimeMs >= (mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS)) { - mPollLceScheduled = false; - if (!mPollLcePending.getAndSet(true)) { - pollLceData(); + mBandwidthUpdateScheduled = false; + if (!mBandwidthUpdatePending.getAndSet(true)) { + onBandwidthUpdateRequested(); } } else { // deliver the request at a later time rather than discard it completely. - if (!mPollLceScheduled) { + if (!mBandwidthUpdateScheduled) { long waitTime = mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS - currentTimeMs + 1; - mPollLceScheduled = sendEmptyMessageDelayed( + mBandwidthUpdateScheduled = sendEmptyMessageDelayed( CMD_REQUEST_BANDWIDTH_UPDATE, waitTime); } } @@ -324,19 +377,20 @@ public abstract class NetworkAgent { + (msg.arg1 == VALID_NETWORK ? "VALID, " : "INVALID, ") + redirectUrl); } - networkStatus(msg.arg1, redirectUrl); + onValidationStatus(msg.arg1 /* status */, redirectUrl); break; } case CMD_SAVE_ACCEPT_UNVALIDATED: { - saveAcceptUnvalidated(msg.arg1 != 0); + onSaveAcceptUnvalidated(msg.arg1 != 0); break; } case CMD_START_SOCKET_KEEPALIVE: { - startSocketKeepalive(msg); + onStartSocketKeepalive(msg.arg1 /* slot */, msg.arg2 /* interval */, + (KeepalivePacketData) msg.obj /* packet */); break; } case CMD_STOP_SOCKET_KEEPALIVE: { - stopSocketKeepalive(msg); + onStopSocketKeepalive(msg.arg1 /* slot */); break; } @@ -349,19 +403,20 @@ public abstract class NetworkAgent { for (int i = 0; i < intThresholds.length; i++) { intThresholds[i] = thresholds.get(i); } - setSignalStrengthThresholds(intThresholds); + onSignalStrengthThresholdsUpdated(intThresholds); break; } case CMD_PREVENT_AUTOMATIC_RECONNECT: { - preventAutomaticReconnect(); + onAutomaticReconnectDisabled(); break; } case CMD_ADD_KEEPALIVE_PACKET_FILTER: { - addKeepalivePacketFilter(msg); + onAddKeepalivePacketFilter(msg.arg1 /* slot */, + (KeepalivePacketData) msg.obj /* packet */); break; } case CMD_REMOVE_KEEPALIVE_PACKET_FILTER: { - removeKeepalivePacketFilter(msg); + onRemoveKeepalivePacketFilter(msg.arg1 /* slot */); break; } } @@ -396,14 +451,16 @@ public abstract class NetworkAgent { } /** - * Called by the bearer code when it has new LinkProperties data. + * Must be called by the agent when the network's {@link LinkProperties} change. + * @param linkProperties the new LinkProperties. */ - public void sendLinkProperties(LinkProperties linkProperties) { + public void sendLinkProperties(@NonNull LinkProperties linkProperties) { queueOrSendMessage(EVENT_NETWORK_PROPERTIES_CHANGED, new LinkProperties(linkProperties)); } /** - * Called by the bearer code when it has new NetworkInfo data. + * Must be called by the agent when it has a new NetworkInfo object. + * @hide TODO: expose something better. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public void sendNetworkInfo(NetworkInfo networkInfo) { @@ -411,17 +468,19 @@ public abstract class NetworkAgent { } /** - * Called by the bearer code when it has new NetworkCapabilities data. + * Must be called by the agent when the network's {@link NetworkCapabilities} change. + * @param networkCapabilities the new NetworkCapabilities. */ - public void sendNetworkCapabilities(NetworkCapabilities networkCapabilities) { - mPollLcePending.set(false); + public void sendNetworkCapabilities(@NonNull NetworkCapabilities networkCapabilities) { + mBandwidthUpdatePending.set(false); mLastBwRefreshTime = System.currentTimeMillis(); queueOrSendMessage(EVENT_NETWORK_CAPABILITIES_CHANGED, new NetworkCapabilities(networkCapabilities)); } /** - * Called by the bearer code when it has a new score for this network. + * Must be called by the agent to update the score of this network. + * @param score the new score. */ public void sendNetworkScore(int score) { if (score < 0) { @@ -433,14 +492,16 @@ public abstract class NetworkAgent { } /** - * Called by the bearer code when it has a new NetworkScore for this network. + * Must be called by the agent when it has a new {@link NetworkScore} for this network. + * @param ns the new score. + * @hide TODO: unhide the NetworkScore class, and rename to sendNetworkScore. */ public void updateScore(@NonNull NetworkScore ns) { queueOrSendMessage(EVENT_NETWORK_SCORE_CHANGED, new NetworkScore(ns)); } /** - * Called by the bearer to indicate this network was manually selected by the user. + * Must be called by the agent to indicate this network was manually selected by the user. * This should be called before the NetworkInfo is marked CONNECTED so that this * Network can be given special treatment at that time. If {@code acceptUnvalidated} is * {@code true}, then the system will switch to this network. If it is {@code false} and the @@ -449,15 +510,16 @@ public abstract class NetworkAgent { * {@link #saveAcceptUnvalidated} to persist the user's choice. Thus, if the transport ever * calls this method with {@code acceptUnvalidated} set to {@code false}, it must also implement * {@link #saveAcceptUnvalidated} to respect the user's choice. + * @hide should move to NetworkAgentConfig. */ public void explicitlySelected(boolean acceptUnvalidated) { explicitlySelected(true /* explicitlySelected */, acceptUnvalidated); } /** - * Called by the bearer to indicate whether the network was manually selected by the user. - * This should be called before the NetworkInfo is marked CONNECTED so that this - * Network can be given special treatment at that time. + * Must be called by the agent to indicate whether the network was manually selected by the + * user. This should be called before the network becomes connected, so it can be given + * special treatment when it does. * * If {@code explicitlySelected} is {@code true}, and {@code acceptUnvalidated} is {@code true}, * then the system will switch to this network. If {@code explicitlySelected} is {@code true} @@ -472,6 +534,7 @@ public abstract class NetworkAgent { * {@code true}, the system will interpret this as the user having accepted partial connectivity * on this network. Thus, the system will switch to the network and consider it validated even * if it only provides partial connectivity, but the network is not otherwise treated specially. + * @hide should move to NetworkAgentConfig. */ public void explicitlySelected(boolean explicitlySelected, boolean acceptUnvalidated) { queueOrSendMessage(EVENT_SET_EXPLICITLY_SELECTED, @@ -485,73 +548,126 @@ public abstract class NetworkAgent { * as well, either canceling NetworkRequests or altering their score such that this * network won't be immediately requested again. */ - abstract protected void unwanted(); + public void onNetworkUnwanted() { + unwanted(); + } + /** @hide TODO delete once subclasses have moved to onNetworkUnwanted. */ + protected void unwanted() { + } /** * Called when ConnectivityService request a bandwidth update. The parent factory * shall try to overwrite this method and produce a bandwidth update if capable. */ + public void onBandwidthUpdateRequested() { + pollLceData(); + } + /** @hide TODO delete once subclasses have moved to onBandwidthUpdateRequested. */ protected void pollLceData() { } /** * Called when the system determines the usefulness of this network. * - * Networks claiming internet connectivity will have their internet - * connectivity verified. + * The system attempts to validate Internet connectivity on networks that provide the + * {@link NetworkCapabilities#NET_CAPABILITY_INTERNET} capability. * * Currently there are two possible values: - * {@code VALID_NETWORK} if the system is happy with the connection, - * {@code INVALID_NETWORK} if the system is not happy. - * TODO - add indications of captive portal-ness and related success/failure, - * ie, CAPTIVE_SUCCESS_NETWORK, CAPTIVE_NETWORK for successful login and detection + * {@code VALIDATION_STATUS_VALID} if Internet connectivity was validated, + * {@code VALIDATION_STATUS_NOT_VALID} if Internet connectivity was not validated. * - * This may be called multiple times as the network status changes and may - * generate false negatives if we lose ip connectivity before the link is torn down. + * This may be called multiple times as network status changes, or if there are multiple + * subsequent attempts to validate connectivity that fail. * - * @param status one of {@code VALID_NETWORK} or {@code INVALID_NETWORK}. - * @param redirectUrl If the Internet probe was redirected, this is the destination it was - * redirected to, otherwise {@code null}. + * @param status one of {@code VALIDATION_STATUS_VALID} or {@code VALIDATION_STATUS_NOT_VALID}. + * @param redirectUrl If Internet connectivity is being redirected (e.g., on a captive portal), + * this is the destination the probes are being redirected to, otherwise {@code null}. */ + public void onValidationStatus(int status, @Nullable String redirectUrl) { + networkStatus(status, redirectUrl); + } + /** @hide TODO delete once subclasses have moved to onValidationStatus */ protected void networkStatus(int status, String redirectUrl) { } + /** * Called when the user asks to remember the choice to use this network even if unvalidated. * The transport is responsible for remembering the choice, and the next time the user connects * to the network, should explicitlySelected with {@code acceptUnvalidated} set to {@code true}. * This method will only be called if {@link #explicitlySelected} was called with * {@code acceptUnvalidated} set to {@code false}. + * @param accept whether the user wants to use the network even if unvalidated. */ + public void onSaveAcceptUnvalidated(boolean accept) { + saveAcceptUnvalidated(accept); + } + /** @hide TODO delete once subclasses have moved to onSaveAcceptUnvalidated */ protected void saveAcceptUnvalidated(boolean accept) { } /** * Requests that the network hardware send the specified packet at the specified interval. - */ + * + * @param slot the hardware slot on which to start the keepalive. + * @param intervalSeconds the interval between packets + * @param packet the packet to send. + */ + public void onStartSocketKeepalive(int slot, int intervalSeconds, + @NonNull KeepalivePacketData packet) { + Message msg = mHandler.obtainMessage(CMD_START_SOCKET_KEEPALIVE, slot, intervalSeconds, + packet); + startSocketKeepalive(msg); + msg.recycle(); + } + /** @hide TODO delete once subclasses have moved to onStartSocketKeepalive */ protected void startSocketKeepalive(Message msg) { onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_UNSUPPORTED); } /** - * Requests that the network hardware send the specified packet at the specified interval. + * Requests that the network hardware stop a previously-started keepalive. + * + * @param slot the hardware slot on which to stop the keepalive. */ + public void onStopSocketKeepalive(int slot) { + Message msg = mHandler.obtainMessage(CMD_STOP_SOCKET_KEEPALIVE, slot, 0, null); + stopSocketKeepalive(msg); + msg.recycle(); + } + /** @hide TODO delete once subclasses have moved to onStopSocketKeepalive */ protected void stopSocketKeepalive(Message msg) { onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_UNSUPPORTED); } /** - * Called by the network when a socket keepalive event occurs. + * Must be called by the agent when a socket keepalive event occurs. + * + * @param slot the hardware slot on which the event occurred. + * @param event the event that occurred. */ + public void sendSocketKeepaliveEvent(int slot, int event) { + queueOrSendMessage(EVENT_SOCKET_KEEPALIVE, slot, event); + } + /** @hide TODO delete once callers have moved to sendSocketKeepaliveEvent */ public void onSocketKeepaliveEvent(int slot, int reason) { - queueOrSendMessage(EVENT_SOCKET_KEEPALIVE, slot, reason); + sendSocketKeepaliveEvent(slot, reason); } /** * Called by ConnectivityService to add specific packet filter to network hardware to block - * ACKs matching the sent keepalive packets. Implementations that support this feature must - * override this method. + * replies (e.g., TCP ACKs) matching the sent keepalive packets. Implementations that support + * this feature must override this method. + * + * @param slot the hardware slot on which the keepalive should be sent. + * @param packet the packet that is being sent. */ + public void onAddKeepalivePacketFilter(int slot, @NonNull KeepalivePacketData packet) { + Message msg = mHandler.obtainMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER, slot, 0, packet); + addKeepalivePacketFilter(msg); + msg.recycle(); + } + /** @hide TODO delete once subclasses have moved to onAddKeepalivePacketFilter */ protected void addKeepalivePacketFilter(Message msg) { } @@ -559,14 +675,28 @@ public abstract class NetworkAgent { * Called by ConnectivityService to remove a packet filter installed with * {@link #addKeepalivePacketFilter(Message)}. Implementations that support this feature * must override this method. + * + * @param slot the hardware slot on which the keepalive is being sent. */ + public void onRemoveKeepalivePacketFilter(int slot) { + Message msg = mHandler.obtainMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER, slot, 0, null); + removeKeepalivePacketFilter(msg); + msg.recycle(); + } + /** @hide TODO delete once subclasses have moved to onRemoveKeepalivePacketFilter */ protected void removeKeepalivePacketFilter(Message msg) { } /** * Called by ConnectivityService to inform this network transport of signal strength thresholds * that when crossed should trigger a system wakeup and a NetworkCapabilities update. + * + * @param thresholds the array of thresholds that should trigger wakeups. */ + public void onSignalStrengthThresholdsUpdated(@NonNull int[] thresholds) { + setSignalStrengthThresholds(thresholds); + } + /** @hide TODO delete once subclasses have moved to onSetSignalStrengthThresholds */ protected void setSignalStrengthThresholds(int[] thresholds) { } @@ -576,9 +706,14 @@ public abstract class NetworkAgent { * responsible for making sure the device does not automatically reconnect to the same network * after the {@code unwanted} call. */ + public void onAutomaticReconnectDisabled() { + preventAutomaticReconnect(); + } + /** @hide TODO delete once subclasses have moved to onAutomaticReconnectDisabled */ protected void preventAutomaticReconnect() { } + /** @hide */ protected void log(String s) { Log.d(LOG_TAG, "NetworkAgent: " + s); } diff --git a/core/java/android/net/NetworkAgentConfig.java b/core/java/android/net/NetworkAgentConfig.java index 3a383a44cdc3..abc6b67efb11 100644 --- a/core/java/android/net/NetworkAgentConfig.java +++ b/core/java/android/net/NetworkAgentConfig.java @@ -18,22 +18,27 @@ package android.net; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import android.text.TextUtils; /** - * A grab-bag of information (metadata, policies, properties, etc) about a - * {@link Network}. Since this contains PII, it should not be sent outside the - * system. + * Allows a network transport to provide the system with policy and configuration information about + * a particular network when registering a {@link NetworkAgent}. This information cannot change once + * the agent is registered. * * @hide */ -public class NetworkAgentConfig implements Parcelable { +@SystemApi +public final class NetworkAgentConfig implements Parcelable { /** * If the {@link Network} is a VPN, whether apps are allowed to bypass the * VPN. This is set by a {@link VpnService} and used by * {@link ConnectivityManager} when creating a VPN. + * + * @hide */ public boolean allowBypass; @@ -43,6 +48,8 @@ public class NetworkAgentConfig implements Parcelable { * ap in the wifi settings to trigger a connection is explicit. A 3rd party app asking to * connect to a particular access point is also explicit, though this may change in the future * as we want apps to use the multinetwork apis. + * + * @hide */ public boolean explicitlySelected; @@ -50,12 +57,16 @@ public class NetworkAgentConfig implements Parcelable { * Set if the user desires to use this network even if it is unvalidated. This field has meaning * only if {@link explicitlySelected} is true. If it is, this field must also be set to the * appropriate value based on previous user choice. + * + * @hide */ public boolean acceptUnvalidated; /** * Whether the user explicitly set that this network should be validated even if presence of * only partial internet connectivity. + * + * @hide */ public boolean acceptPartialConnectivity; @@ -65,29 +76,62 @@ public class NetworkAgentConfig implements Parcelable { * procedure, a carrier specific provisioning notification will be placed. * only one notification should be displayed. This field is set based on * which notification should be used for provisioning. + * + * @hide */ public boolean provisioningNotificationDisabled; /** + * + * @return whether the sign in to network notification is enabled by this configuration. + */ + public boolean isProvisioningNotificationEnabled() { + return !provisioningNotificationDisabled; + } + + /** * For mobile networks, this is the subscriber ID (such as IMSI). + * + * @hide */ public String subscriberId; /** + * @return the subscriber ID, or null if none. + */ + @Nullable + public String getSubscriberId() { + return subscriberId; + } + + /** * Set to skip 464xlat. This means the device will treat the network as IPv6-only and * will not attempt to detect a NAT64 via RFC 7050 DNS lookups. + * + * @hide */ public boolean skip464xlat; /** + * @return whether NAT64 prefix detection is enabled. + */ + public boolean isNat64DetectionEnabled() { + return !skip464xlat; + } + + /** * Set to true if the PRIVATE_DNS_BROKEN notification has shown for this network. * Reset this bit when private DNS mode is changed from strict mode to opportunistic/off mode. + * + * @hide */ public boolean hasShownBroken; + /** @hide */ public NetworkAgentConfig() { } + /** @hide */ public NetworkAgentConfig(@Nullable NetworkAgentConfig nac) { if (nac != null) { allowBypass = nac.allowBypass; @@ -99,13 +143,63 @@ public class NetworkAgentConfig implements Parcelable { } } + /** + * Builder class to facilitate constructing {@link NetworkAgentConfig} objects. + */ + public static class Builder { + private final NetworkAgentConfig mConfig = new NetworkAgentConfig(); + + /** + * Sets the subscriber ID for this network. + * + * @return this builder, to facilitate chaining. + */ + @NonNull + public Builder setSubscriberId(@Nullable String subscriberId) { + mConfig.subscriberId = subscriberId; + return this; + } + + /** + * Disables active detection of NAT64 (e.g., via RFC 7050 DNS lookups). Used to save power + * and reduce idle traffic on networks that are known to be IPv6-only without a NAT64. + * + * @return this builder, to facilitate chaining. + */ + @NonNull + public Builder disableNat64Detection() { + mConfig.skip464xlat = true; + return this; + } + + /** + * Disables the "Sign in to network" notification. Used if the network transport will + * perform its own carrier-specific provisioning procedure. + * + * @return this builder, to facilitate chaining. + */ + @NonNull + public Builder disableProvisioningNotification() { + mConfig.provisioningNotificationDisabled = true; + return this; + } + + /** + * Returns the constructed {@link NetworkAgentConfig} object. + */ + @NonNull + public NetworkAgentConfig build() { + return mConfig; + } + } + @Override public int describeContents() { return 0; } @Override - public void writeToParcel(Parcel out, int flags) { + public void writeToParcel(@NonNull Parcel out, int flags) { out.writeInt(allowBypass ? 1 : 0); out.writeInt(explicitlySelected ? 1 : 0); out.writeInt(acceptUnvalidated ? 1 : 0); diff --git a/core/java/android/net/NetworkFactory.java b/core/java/android/net/NetworkFactory.java index 824ddb8dd260..e27103755e6d 100644 --- a/core/java/android/net/NetworkFactory.java +++ b/core/java/android/net/NetworkFactory.java @@ -115,13 +115,6 @@ public class NetworkFactory extends Handler { */ private static final int CMD_SET_FILTER = BASE + 3; - /** - * Sent by NetworkFactory to ConnectivityService to indicate that a request is - * unfulfillable. - * @see #releaseRequestAsUnfulfillableByAnyFactory(NetworkRequest). - */ - public static final int EVENT_UNFULFILLABLE_REQUEST = BASE + 4; - private final Context mContext; private final ArrayList<Message> mPreConnectedQueue = new ArrayList<Message>(); private final String LOG_TAG; diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java index 9731f3ca186d..301d20340643 100644 --- a/core/java/android/net/NetworkRequest.java +++ b/core/java/android/net/NetworkRequest.java @@ -300,22 +300,34 @@ public class NetworkRequest implements Parcelable { * this without a single transport set will generate an exception, as will * subsequently adding or removing transports after this is set. * </p> - * The interpretation of this {@code String} is bearer specific and bearers that use - * it should document their particulars. For example, Bluetooth may use some sort of - * device id while WiFi could used ssid and/or bssid. Cellular may use carrier spn. + * If the {@code networkSpecifier} is provided, it shall be interpreted as follows: + * <ul> + * <li>If the specifier can be parsed as an integer, it will be treated as a + * {@link android.net TelephonyNetworkSpecifier}, and the provided integer will be + * interpreted as a SubscriptionId. + * <li>If the value is an ethernet interface name, it will be treated as such. + * <li>For all other cases, the behavior is undefined. + * </ul> * - * @param networkSpecifier An {@code String} of opaque format used to specify the bearer - * specific network specifier where the bearer has a choice of - * networks. + * @param networkSpecifier A {@code String} of either a SubscriptionId in cellular + * network request or an ethernet interface name in ethernet + * network request. + * + * @deprecated Use {@link #setNetworkSpecifier(NetworkSpecifier)} instead. */ + @Deprecated public Builder setNetworkSpecifier(String networkSpecifier) { - /* - * A StringNetworkSpecifier does not accept null or empty ("") strings. When network - * specifiers were strings a null string and an empty string were considered equivalent. - * Hence no meaning is attached to a null or empty ("") string. - */ - return setNetworkSpecifier(TextUtils.isEmpty(networkSpecifier) ? null - : new StringNetworkSpecifier(networkSpecifier)); + try { + int subId = Integer.parseInt(networkSpecifier); + return setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder() + .setSubscriptionId(subId).build()); + } catch (NumberFormatException nfe) { + // A StringNetworkSpecifier does not accept null or empty ("") strings. When network + // specifiers were strings a null string and an empty string were considered + // equivalent. Hence no meaning is attached to a null or empty ("") string. + return setNetworkSpecifier(TextUtils.isEmpty(networkSpecifier) ? null + : new StringNetworkSpecifier(networkSpecifier)); + } } /** @@ -455,6 +467,19 @@ public class NetworkRequest implements Parcelable { } /** + * Returns true iff. the capabilities requested in this NetworkRequest are satisfied by the + * provided {@link NetworkCapabilities}. + * + * @param nc Capabilities that should satisfy this NetworkRequest. null capabilities do not + * satisfy any request. + * @hide + */ + @SystemApi + public boolean satisfiedBy(@Nullable NetworkCapabilities nc) { + return networkCapabilities.satisfiedByNetworkCapabilities(nc); + } + + /** * @see Builder#addTransportType(int) */ public boolean hasTransport(@Transport int transportType) { diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java index f6dc52522cb2..c233ec0e52cf 100644 --- a/core/java/android/net/NetworkScoreManager.java +++ b/core/java/android/net/NetworkScoreManager.java @@ -163,27 +163,26 @@ public class NetworkScoreManager { public static final String EXTRA_NEW_SCORER = "newScorer"; /** @hide */ - @IntDef({CACHE_FILTER_NONE, CACHE_FILTER_CURRENT_NETWORK, CACHE_FILTER_SCAN_RESULTS}) + @IntDef({SCORE_FILTER_NONE, SCORE_FILTER_CURRENT_NETWORK, SCORE_FILTER_SCAN_RESULTS}) @Retention(RetentionPolicy.SOURCE) - public @interface CacheUpdateFilter {} + public @interface ScoreUpdateFilter {} /** - * Do not filter updates sent to the cache. - * @hide + * Do not filter updates sent to the {@link NetworkScoreCallback}]. */ - public static final int CACHE_FILTER_NONE = 0; + public static final int SCORE_FILTER_NONE = 0; /** - * Only send cache updates when the network matches the connected network. - * @hide + * Only send updates to the {@link NetworkScoreCallback} when the network matches the connected + * network. */ - public static final int CACHE_FILTER_CURRENT_NETWORK = 1; + public static final int SCORE_FILTER_CURRENT_NETWORK = 1; /** - * Only send cache updates when the network is part of the current scan result set. - * @hide + * Only send updates to the {@link NetworkScoreCallback} when the network is part of the + * current scan result set. */ - public static final int CACHE_FILTER_SCAN_RESULTS = 2; + public static final int SCORE_FILTER_SCAN_RESULTS = 2; /** @hide */ @IntDef({RECOMMENDATIONS_ENABLED_FORCED_OFF, RECOMMENDATIONS_ENABLED_OFF, @@ -404,13 +403,13 @@ public class NetworkScoreManager { * @throws SecurityException if the caller does not hold the * {@link permission#REQUEST_NETWORK_SCORES} permission. * @throws IllegalArgumentException if a score cache is already registered for this type. - * @deprecated equivalent to registering for cache updates with CACHE_FILTER_NONE. + * @deprecated equivalent to registering for cache updates with {@link #SCORE_FILTER_NONE}. * @hide */ @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES) @Deprecated // migrate to registerNetworkScoreCache(int, INetworkScoreCache, int) public void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) { - registerNetworkScoreCache(networkType, scoreCache, CACHE_FILTER_NONE); + registerNetworkScoreCache(networkType, scoreCache, SCORE_FILTER_NONE); } /** @@ -418,7 +417,7 @@ public class NetworkScoreManager { * * @param networkType the type of network this cache can handle. See {@link NetworkKey#type} * @param scoreCache implementation of {@link INetworkScoreCache} to store the scores - * @param filterType the {@link CacheUpdateFilter} to apply + * @param filterType the {@link ScoreUpdateFilter} to apply * @throws SecurityException if the caller does not hold the * {@link permission#REQUEST_NETWORK_SCORES} permission. * @throws IllegalArgumentException if a score cache is already registered for this type. @@ -426,7 +425,7 @@ public class NetworkScoreManager { */ @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES) public void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache, - @CacheUpdateFilter int filterType) { + @ScoreUpdateFilter int filterType) { try { mService.registerNetworkScoreCache(networkType, scoreCache, filterType); } catch (RemoteException e) { @@ -510,7 +509,7 @@ public class NetworkScoreManager { * Register a network score callback. * * @param networkType the type of network this cache can handle. See {@link NetworkKey#type} - * @param filterType the {@link CacheUpdateFilter} to apply + * @param filterType the {@link ScoreUpdateFilter} to apply * @param callback implementation of {@link NetworkScoreCallback} that will be invoked when the * scores change. * @param executor The executor on which to execute the callbacks. @@ -522,7 +521,7 @@ public class NetworkScoreManager { @SystemApi @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES) public void registerNetworkScoreCallback(@NetworkKey.NetworkType int networkType, - @CacheUpdateFilter int filterType, + @ScoreUpdateFilter int filterType, @NonNull @CallbackExecutor Executor executor, @NonNull NetworkScoreCallback callback) throws SecurityException { if (callback == null || executor == null) { diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index 08cc4e24b245..779f7bc91e8f 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -31,7 +31,6 @@ import android.util.Pair; import java.io.FileDescriptor; import java.math.BigInteger; import java.net.Inet4Address; -import java.net.Inet6Address; import java.net.InetAddress; import java.net.SocketException; import java.net.UnknownHostException; @@ -313,15 +312,6 @@ public class NetworkUtils { } /** - * Check if IP address type is consistent between two InetAddress. - * @return true if both are the same type. False otherwise. - */ - public static boolean addressTypeMatches(InetAddress left, InetAddress right) { - return (((left instanceof Inet4Address) && (right instanceof Inet4Address)) || - ((left instanceof Inet6Address) && (right instanceof Inet6Address))); - } - - /** * Convert a 32 char hex string into a Inet6Address. * throws a runtime exception if the string isn't 32 chars, isn't hex or can't be * made into an Inet6Address diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java index ea6002ca29e6..e08809457649 100644 --- a/core/java/android/net/RouteInfo.java +++ b/core/java/android/net/RouteInfo.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; +import android.net.util.NetUtils; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; @@ -441,21 +442,7 @@ public final class RouteInfo implements Parcelable { @UnsupportedAppUsage @Nullable public static RouteInfo selectBestRoute(Collection<RouteInfo> routes, InetAddress dest) { - if ((routes == null) || (dest == null)) return null; - - RouteInfo bestRoute = null; - // pick a longest prefix match under same address type - for (RouteInfo route : routes) { - if (NetworkUtils.addressTypeMatches(route.mDestination.getAddress(), dest)) { - if ((bestRoute != null) && - (bestRoute.mDestination.getPrefixLength() >= - route.mDestination.getPrefixLength())) { - continue; - } - if (route.matches(dest)) bestRoute = route; - } - } - return bestRoute; + return NetUtils.selectBestRoute(routes, dest); } /** diff --git a/core/java/android/net/SocketKeepalive.java b/core/java/android/net/SocketKeepalive.java index fb224fbe1318..fc9a8f63c131 100644 --- a/core/java/android/net/SocketKeepalive.java +++ b/core/java/android/net/SocketKeepalive.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.os.Binder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; @@ -53,7 +54,11 @@ import java.util.concurrent.Executor; public abstract class SocketKeepalive implements AutoCloseable { static final String TAG = "SocketKeepalive"; - /** @hide */ + /** + * No errors. + * @hide + */ + @SystemApi public static final int SUCCESS = 0; /** @hide */ diff --git a/core/java/android/net/TelephonyNetworkSpecifier.java b/core/java/android/net/TelephonyNetworkSpecifier.java new file mode 100644 index 000000000000..726f77059707 --- /dev/null +++ b/core/java/android/net/TelephonyNetworkSpecifier.java @@ -0,0 +1,146 @@ +/* + * 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.Parcel; +import android.os.Parcelable; + +/** + * NetworkSpecifier object for cellular network request. Apps should use the + * {@link TelephonyNetworkSpecifier.Builder} class to create an instance. + */ +public final class TelephonyNetworkSpecifier extends NetworkSpecifier implements Parcelable { + + private final int mSubId; + + /** + * Return the subscription Id of current TelephonyNetworkSpecifier object. + * + * @return The subscription id. + */ + public int getSubscriptionId() { + return mSubId; + } + + /** + * @hide + */ + public TelephonyNetworkSpecifier(int subId) { + this.mSubId = subId; + } + + public static final @NonNull Creator<TelephonyNetworkSpecifier> CREATOR = + new Creator<TelephonyNetworkSpecifier>() { + @Override + public TelephonyNetworkSpecifier createFromParcel(Parcel in) { + int subId = in.readInt(); + return new TelephonyNetworkSpecifier(subId); + } + + @Override + public TelephonyNetworkSpecifier[] newArray(int size) { + return new TelephonyNetworkSpecifier[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mSubId); + } + + @Override + public int hashCode() { + return mSubId; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof TelephonyNetworkSpecifier)) { + return false; + } + + TelephonyNetworkSpecifier lhs = (TelephonyNetworkSpecifier) obj; + return mSubId == lhs.mSubId; + } + + @Override + public String toString() { + return new StringBuilder() + .append("TelephonyNetworkSpecifier [") + .append("mSubId = ").append(mSubId) + .append("]") + .toString(); + } + + /** @hide */ + @Override + public boolean satisfiedBy(NetworkSpecifier other) { + // Any generic requests should be satisfied by a specific telephony network. + // For simplicity, we treat null same as MatchAllNetworkSpecifier + return equals(other) || other == null || other instanceof MatchAllNetworkSpecifier; + } + + + /** + * Builder to create {@link TelephonyNetworkSpecifier} object. + */ + public static final class Builder { + // Integer.MIN_VALUE which is not a valid subId, services as the sentinel to check if + // subId was set + private static final int SENTINEL_SUB_ID = Integer.MIN_VALUE; + + private int mSubId; + + public Builder() { + mSubId = SENTINEL_SUB_ID; + } + + /** + * Set the subscription id. + * + * @param subId The subscription Id. + * @return Instance of {@link Builder} to enable the chaining of the builder method. + */ + public @NonNull Builder setSubscriptionId(int subId) { + mSubId = subId; + return this; + } + + /** + * Create a NetworkSpecifier for the cellular network request. + * + * @return TelephonyNetworkSpecifier object. + * @throws IllegalArgumentException when subscription Id is not provided through + * {@link #setSubscriptionId(int)}. + */ + public @NonNull TelephonyNetworkSpecifier build() { + if (mSubId == SENTINEL_SUB_ID) { + throw new IllegalArgumentException("Subscription Id is not provided."); + } + return new TelephonyNetworkSpecifier(mSubId); + } + } +} diff --git a/core/java/android/net/nsd/DnsSdTxtRecord.java b/core/java/android/net/nsd/DnsSdTxtRecord.java deleted file mode 100644 index e4a91c5798f1..000000000000 --- a/core/java/android/net/nsd/DnsSdTxtRecord.java +++ /dev/null @@ -1,325 +0,0 @@ -/* -*- Mode: Java; tab-width: 4 -*- - * - * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. - * - * 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. - - To do: - - implement remove() - - fix set() to replace existing values - */ - -package android.net.nsd; - -import android.os.Parcelable; -import android.os.Parcel; - -import java.util.Arrays; - -/** - * This class handles TXT record data for DNS based service discovery as specified at - * http://tools.ietf.org/html/draft-cheshire-dnsext-dns-sd-11 - * - * DNS-SD specifies that a TXT record corresponding to an SRV record consist of - * a packed array of bytes, each preceded by a length byte. Each string - * is an attribute-value pair. - * - * The DnsSdTxtRecord object stores the entire TXT data as a single byte array, traversing it - * as need be to implement its various methods. - * @hide - * - */ -public class DnsSdTxtRecord implements Parcelable { - private static final byte mSeperator = '='; - - private byte[] mData; - - /** Constructs a new, empty TXT record. */ - public DnsSdTxtRecord() { - mData = new byte[0]; - } - - /** Constructs a new TXT record from a byte array in the standard format. */ - public DnsSdTxtRecord(byte[] data) { - mData = (byte[]) data.clone(); - } - - /** Copy constructor */ - public DnsSdTxtRecord(DnsSdTxtRecord src) { - if (src != null && src.mData != null) { - mData = (byte[]) src.mData.clone(); - } - } - - /** - * Set a key/value pair. Setting an existing key will replace its value. - * @param key Must be ascii with no '=' - * @param value matching value to key - */ - public void set(String key, String value) { - byte[] keyBytes; - byte[] valBytes; - int valLen; - - if (value != null) { - valBytes = value.getBytes(); - valLen = valBytes.length; - } else { - valBytes = null; - valLen = 0; - } - - try { - keyBytes = key.getBytes("US-ASCII"); - } - catch (java.io.UnsupportedEncodingException e) { - throw new IllegalArgumentException("key should be US-ASCII"); - } - - for (int i = 0; i < keyBytes.length; i++) { - if (keyBytes[i] == '=') { - throw new IllegalArgumentException("= is not a valid character in key"); - } - } - - if (keyBytes.length + valLen >= 255) { - throw new IllegalArgumentException("Key and Value length cannot exceed 255 bytes"); - } - - int currentLoc = remove(key); - if (currentLoc == -1) - currentLoc = keyCount(); - - insert(keyBytes, valBytes, currentLoc); - } - - /** - * Get a value for a key - * - * @param key - * @return The value associated with the key - */ - public String get(String key) { - byte[] val = this.getValue(key); - return val != null ? new String(val) : null; - } - - /** Remove a key/value pair. If found, returns the index or -1 if not found */ - public int remove(String key) { - int avStart = 0; - - for (int i=0; avStart < mData.length; i++) { - int avLen = mData[avStart]; - if (key.length() <= avLen && - (key.length() == avLen || mData[avStart + key.length() + 1] == mSeperator)) { - String s = new String(mData, avStart + 1, key.length()); - if (0 == key.compareToIgnoreCase(s)) { - byte[] oldBytes = mData; - mData = new byte[oldBytes.length - avLen - 1]; - System.arraycopy(oldBytes, 0, mData, 0, avStart); - System.arraycopy(oldBytes, avStart + avLen + 1, mData, avStart, - oldBytes.length - avStart - avLen - 1); - return i; - } - } - avStart += (0xFF & (avLen + 1)); - } - return -1; - } - - /** Return the count of keys */ - public int keyCount() { - int count = 0, nextKey; - for (nextKey = 0; nextKey < mData.length; count++) { - nextKey += (0xFF & (mData[nextKey] + 1)); - } - return count; - } - - /** Return true if key is present, false if not. */ - public boolean contains(String key) { - String s = null; - for (int i = 0; null != (s = this.getKey(i)); i++) { - if (0 == key.compareToIgnoreCase(s)) return true; - } - return false; - } - - /* Gets the size in bytes */ - public int size() { - return mData.length; - } - - /* Gets the raw data in bytes */ - public byte[] getRawData() { - return (byte[]) mData.clone(); - } - - private void insert(byte[] keyBytes, byte[] value, int index) { - byte[] oldBytes = mData; - int valLen = (value != null) ? value.length : 0; - int insertion = 0; - int newLen, avLen; - - for (int i = 0; i < index && insertion < mData.length; i++) { - insertion += (0xFF & (mData[insertion] + 1)); - } - - avLen = keyBytes.length + valLen + (value != null ? 1 : 0); - newLen = avLen + oldBytes.length + 1; - - mData = new byte[newLen]; - System.arraycopy(oldBytes, 0, mData, 0, insertion); - int secondHalfLen = oldBytes.length - insertion; - System.arraycopy(oldBytes, insertion, mData, newLen - secondHalfLen, secondHalfLen); - mData[insertion] = (byte) avLen; - System.arraycopy(keyBytes, 0, mData, insertion + 1, keyBytes.length); - if (value != null) { - mData[insertion + 1 + keyBytes.length] = mSeperator; - System.arraycopy(value, 0, mData, insertion + keyBytes.length + 2, valLen); - } - } - - /** Return a key in the TXT record by zero-based index. Returns null if index exceeds the total number of keys. */ - private String getKey(int index) { - int avStart = 0; - - for (int i=0; i < index && avStart < mData.length; i++) { - avStart += mData[avStart] + 1; - } - - if (avStart < mData.length) { - int avLen = mData[avStart]; - int aLen = 0; - - for (aLen=0; aLen < avLen; aLen++) { - if (mData[avStart + aLen + 1] == mSeperator) break; - } - return new String(mData, avStart + 1, aLen); - } - return null; - } - - /** - * Look up a key in the TXT record by zero-based index and return its value. - * Returns null if index exceeds the total number of keys. - * Returns null if the key is present with no value. - */ - private byte[] getValue(int index) { - int avStart = 0; - byte[] value = null; - - for (int i=0; i < index && avStart < mData.length; i++) { - avStart += mData[avStart] + 1; - } - - if (avStart < mData.length) { - int avLen = mData[avStart]; - int aLen = 0; - - for (aLen=0; aLen < avLen; aLen++) { - if (mData[avStart + aLen + 1] == mSeperator) { - value = new byte[avLen - aLen - 1]; - System.arraycopy(mData, avStart + aLen + 2, value, 0, avLen - aLen - 1); - break; - } - } - } - return value; - } - - private String getValueAsString(int index) { - byte[] value = this.getValue(index); - return value != null ? new String(value) : null; - } - - private byte[] getValue(String forKey) { - String s = null; - int i; - - for (i = 0; null != (s = this.getKey(i)); i++) { - if (0 == forKey.compareToIgnoreCase(s)) { - return this.getValue(i); - } - } - - return null; - } - - /** - * Return a string representation. - * Example : {key1=value1},{key2=value2}.. - * - * For a key say like "key3" with null value - * {key1=value1},{key2=value2}{key3} - */ - public String toString() { - String a, result = null; - - for (int i = 0; null != (a = this.getKey(i)); i++) { - String av = "{" + a; - String val = this.getValueAsString(i); - if (val != null) - av += "=" + val + "}"; - else - av += "}"; - if (result == null) - result = av; - else - result = result + ", " + av; - } - return result != null ? result : ""; - } - - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - if (!(o instanceof DnsSdTxtRecord)) { - return false; - } - - DnsSdTxtRecord record = (DnsSdTxtRecord)o; - return Arrays.equals(record.mData, mData); - } - - @Override - public int hashCode() { - return Arrays.hashCode(mData); - } - - /** Implement the Parcelable interface */ - public int describeContents() { - return 0; - } - - /** Implement the Parcelable interface */ - public void writeToParcel(Parcel dest, int flags) { - dest.writeByteArray(mData); - } - - /** Implement the Parcelable interface */ - public static final @android.annotation.NonNull Creator<DnsSdTxtRecord> CREATOR = - new Creator<DnsSdTxtRecord>() { - public DnsSdTxtRecord createFromParcel(Parcel in) { - DnsSdTxtRecord info = new DnsSdTxtRecord(); - in.readByteArray(info.mData); - return info; - } - - public DnsSdTxtRecord[] newArray(int size) { - return new DnsSdTxtRecord[size]; - } - }; -} diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java index b0c2546e0300..ac70b523bcc1 100644 --- a/core/java/android/os/BinderProxy.java +++ b/core/java/android/os/BinderProxy.java @@ -631,10 +631,12 @@ public final class BinderProxy implements IBinder { } } - private static void sendDeathNotice(DeathRecipient recipient) { - if (false) Log.v("JavaBinder", "sendDeathNotice to " + recipient); + private static void sendDeathNotice(DeathRecipient recipient, IBinder binderProxy) { + if (false) { + Log.v("JavaBinder", "sendDeathNotice to " + recipient + " for " + binderProxy); + } try { - recipient.binderDied(); + recipient.binderDied(binderProxy); } catch (RuntimeException exc) { Log.w("BinderNative", "Uncaught exception from death notification", exc); diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java index f336fdab5941..f5fe9c3334bd 100644 --- a/core/java/android/os/IBinder.java +++ b/core/java/android/os/IBinder.java @@ -285,6 +285,13 @@ public interface IBinder { */ public interface DeathRecipient { public void binderDied(); + + /** + * @hide + */ + default void binderDied(IBinder who) { + binderDied(); + } } /** diff --git a/core/java/android/os/IIncidentDumpCallback.aidl b/core/java/android/os/IIncidentDumpCallback.aidl new file mode 100644 index 000000000000..09b5b01367c1 --- /dev/null +++ b/core/java/android/os/IIncidentDumpCallback.aidl @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import android.os.ParcelFileDescriptor; + +/** + * Callback from IIncidentManager to dump an extended section. + * + * @hide + */ +oneway interface IIncidentDumpCallback { + /** + * Dumps section data to the given ParcelFileDescriptor. + */ + void onDumpSection(in ParcelFileDescriptor fd); +} diff --git a/core/java/android/os/IIncidentManager.aidl b/core/java/android/os/IIncidentManager.aidl index 7e1b1e0abaf6..923234eb62ab 100644 --- a/core/java/android/os/IIncidentManager.aidl +++ b/core/java/android/os/IIncidentManager.aidl @@ -17,6 +17,7 @@ package android.os; import android.os.IIncidentReportStatusListener; +import android.os.IIncidentDumpCallback; import android.os.IncidentManager; import android.os.IncidentReportArgs; @@ -52,6 +53,19 @@ interface IIncidentManager { @nullable IIncidentReportStatusListener listener); /** + * Register a section callback with the given id and name. The callback function + * will be invoked when an incident report with all sections or sections matching + * the given id is being taken. + */ + oneway void registerSection(int id, String name, IIncidentDumpCallback callback); + + /** + * Unregister a section callback associated with the given id. The section must be + * previously registered with registerSection(int, String, IIncidentDumpCallback). + */ + oneway void unregisterSection(int id); + + /** * Tell the incident daemon that the android system server is up and running. */ oneway void systemRunning(); diff --git a/core/java/android/os/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl index 7b2d148bfa37..416d69229536 100644 --- a/core/java/android/os/IVibratorService.aidl +++ b/core/java/android/os/IVibratorService.aidl @@ -24,7 +24,7 @@ interface IVibratorService { boolean hasVibrator(); boolean hasAmplitudeControl(); - boolean setAlwaysOnEffect(int id, in VibrationEffect effect, + boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, in VibrationEffect effect, in VibrationAttributes attributes); void vibrate(int uid, String opPkg, in VibrationEffect effect, in VibrationAttributes attributes, String reason, IBinder token); diff --git a/core/java/android/os/IncidentManager.java b/core/java/android/os/IncidentManager.java index 09e1c0f6ab1f..f6563ebdbbf4 100644 --- a/core/java/android/os/IncidentManager.java +++ b/core/java/android/os/IncidentManager.java @@ -31,6 +31,7 @@ import android.util.Slog; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -421,6 +422,39 @@ public class IncidentManager { } /** + * Callback for dumping an extended (usually vendor-supplied) incident report section + * + * @see #registerSection + * @see #unregisterSection + * + * @hide + */ + public static class DumpCallback { + private Executor mExecutor; + + IIncidentDumpCallback.Stub mBinder = new IIncidentDumpCallback.Stub() { + @Override + public void onDumpSection(ParcelFileDescriptor pfd) { + if (mExecutor != null) { + mExecutor.execute(() -> { + DumpCallback.this.onDumpSection( + new ParcelFileDescriptor.AutoCloseOutputStream(pfd)); + }); + } else { + DumpCallback.this.onDumpSection( + new ParcelFileDescriptor.AutoCloseOutputStream(pfd)); + } + } + }; + + /** + * Called when incidentd requests to dump this section. + */ + public void onDumpSection(OutputStream out) { + } + } + + /** * @hide */ public IncidentManager(Context context) { @@ -528,6 +562,61 @@ public class IncidentManager { } /** + * Register a callback to dump an extended incident report section with the given id and name. + * The callback function will be invoked when an incident report with all sections or sections + * matching the given id is being taken. + * + * @hide + */ + public void registerSection(int id, String name, @NonNull DumpCallback callback) { + registerSection(id, name, mContext.getMainExecutor(), callback); + } + + /** + * Register a callback to dump an extended incident report section with the given id and name, + * running on the supplied executor. + * + * @hide + */ + public void registerSection(int id, String name, @NonNull @CallbackExecutor Executor executor, + @NonNull DumpCallback callback) { + try { + if (callback.mExecutor != null) { + throw new RuntimeException("Do not reuse DumpCallback objects when calling" + + " registerSection"); + } + callback.mExecutor = executor; + final IIncidentManager service = getIIncidentManagerLocked(); + if (service == null) { + Slog.e(TAG, "registerSection can't find incident binder service"); + return; + } + service.registerSection(id, name, callback.mBinder); + } catch (RemoteException ex) { + Slog.e(TAG, "registerSection failed", ex); + } + } + + /** + * Unregister an extended section dump function. The section must be previously registered with + * {@link #registerSection(int, String, DumpCallback)} + * + * @hide + */ + public void unregisterSection(int id) { + try { + final IIncidentManager service = getIIncidentManagerLocked(); + if (service == null) { + Slog.e(TAG, "unregisterSection can't find incident binder service"); + return; + } + service.unregisterSection(id); + } catch (RemoteException ex) { + Slog.e(TAG, "unregisterSection failed", ex); + } + } + + /** * Get the incident reports that are available for upload for the supplied * broadcast recevier. * diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java index c1542c7ee68f..8050454a8ac3 100644 --- a/core/java/android/os/SystemVibrator.java +++ b/core/java/android/os/SystemVibrator.java @@ -70,14 +70,15 @@ public class SystemVibrator extends Vibrator { } @Override - public boolean setAlwaysOnEffect(int id, VibrationEffect effect, AudioAttributes attributes) { + public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, VibrationEffect effect, + AudioAttributes attributes) { if (mService == null) { Log.w(TAG, "Failed to set always-on effect; no vibrator service."); return false; } try { VibrationAttributes atr = new VibrationAttributes.Builder(attributes, effect).build(); - return mService.setAlwaysOnEffect(id, effect, atr); + return mService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, effect, atr); } catch (RemoteException e) { Log.w(TAG, "Failed to set always-on effect.", e); } diff --git a/core/java/android/os/TimestampedValue.java b/core/java/android/os/TimestampedValue.java index 348574ed43c7..f4c87ac9dfc9 100644 --- a/core/java/android/os/TimestampedValue.java +++ b/core/java/android/os/TimestampedValue.java @@ -18,6 +18,7 @@ package android.os; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import java.util.Objects; @@ -35,19 +36,27 @@ import java.util.Objects; * @param <T> the type of the value with an associated timestamp * @hide */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public final class TimestampedValue<T> implements Parcelable { private final long mReferenceTimeMillis; + @Nullable private final T mValue; - public TimestampedValue(long referenceTimeMillis, T value) { + public TimestampedValue(long referenceTimeMillis, @Nullable T value) { mReferenceTimeMillis = referenceTimeMillis; mValue = value; } + /** Returns the reference time value. See {@link TimestampedValue} for more information. */ public long getReferenceTimeMillis() { return mReferenceTimeMillis; } + /** + * Returns the value associated with the timestamp. See {@link TimestampedValue} for more + * information. + */ + @Nullable public T getValue() { return mValue; } @@ -86,6 +95,8 @@ public final class TimestampedValue<T> implements Parcelable { return one.mReferenceTimeMillis - two.mReferenceTimeMillis; } + /** @hide */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final @NonNull Parcelable.Creator<TimestampedValue<?>> CREATOR = new Parcelable.ClassLoaderCreator<TimestampedValue<?>>() { diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index d8fadfb41189..2eaefca0efa3 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -192,7 +192,11 @@ public class UserManager { /** * Specifies if a user is disallowed from changing Wi-Fi * access points. The default value is <code>false</code>. - * <p>This restriction has no effect in a managed profile. + * <p> + * Device owner and profile owner can set this restriction, although the restriction has no + * effect in a managed profile. When it is set by the profile owner of an organization-owned + * managed profile on the parent profile, it will disallow the personal user from changing + * Wi-Fi access points. * * <p>Key for user restrictions. * <p>Type: Boolean @@ -242,8 +246,13 @@ public class UserManager { /** * Specifies if a user is disallowed from turning on location sharing. * The default value is <code>false</code>. - * <p>In a managed profile, location sharing always reflects the primary user's setting, but + * <p> + * In a managed profile, location sharing always reflects the primary user's setting, but * can be overridden and forced off by setting this restriction to true in the managed profile. + * <p> + * Device owner and profile owner can set this restriction. When it is set by the profile + * owner of an organization-owned managed profile on the parent profile, it will prevent the + * user from turning on location sharing in the personal profile. * * <p>Key for user restrictions. * <p>Type: Boolean @@ -349,9 +358,14 @@ public class UserManager { * Specifies if a user is disallowed from configuring bluetooth. * This does <em>not</em> restrict the user from turning bluetooth on or off. * The default value is <code>false</code>. - * <p>This restriction doesn't prevent the user from using bluetooth. For disallowing usage of + * <p> + * This restriction doesn't prevent the user from using bluetooth. For disallowing usage of * bluetooth completely on the device, use {@link #DISALLOW_BLUETOOTH}. - * <p>This restriction has no effect in a managed profile. + * <p> + * Device owner and profile owner can set this restriction, although the restriction has no + * effect in a managed profile. When it is set by the profile owner of an organization-owned + * managed profile on the parent profile, it will disallow the personal user from configuring + * bluetooth. * * <p>Key for user restrictions. * <p>Type: Boolean @@ -364,8 +378,10 @@ public class UserManager { /** * Specifies if bluetooth is disallowed on the device. * - * <p> This restriction can only be set by the device owner and the profile owner on the - * primary user and it applies globally - i.e. it disables bluetooth on the entire device. + * <p> This restriction can only be set by the device owner, the profile owner on the + * primary user or the profile owner of an organization-owned managed profile on the + * parent profile and it applies globally - i.e. it disables bluetooth on the entire + * device. * <p>The default value is <code>false</code>. * <p>Key for user restrictions. * <p>Type: Boolean @@ -377,8 +393,9 @@ public class UserManager { /** * Specifies if outgoing bluetooth sharing is disallowed on the device. Device owner and profile - * owner can set this restriction. When it is set by device owner, all users on this device will - * be affected. + * owner can set this restriction. When it is set by device owner or the profile owner of an + * organization-owned managed profile on the parent profile, all users on this device will be + * affected. * * <p>Default is <code>true</code> for managed profiles and false for otherwise. When a device * upgrades to {@link android.os.Build.VERSION_CODES#O}, the system sets it for all existing @@ -394,7 +411,8 @@ public class UserManager { /** * Specifies if a user is disallowed from transferring files over - * USB. This can only be set by device owners and profile owners on the primary user. + * USB. This can only be set by device owners, profile owners on the primary user or + * profile owners of organization-owned managed profiles on the parent profile. * The default value is <code>false</code>. * * <p>Key for user restrictions. @@ -453,8 +471,9 @@ public class UserManager { /** * Specifies if a user is disallowed from enabling or accessing debugging features. When set on - * the primary user, disables debugging features altogether, including USB debugging. When set - * on a managed profile or a secondary user, blocks debugging for that user only, including + * the primary user or by the profile owner of an organization-owned managed profile on the + * parent profile, disables debugging features altogether, including USB debugging. When set on + * a managed profile or a secondary user, blocks debugging for that user only, including * starting activities, making service calls, accessing content providers, sending broadcasts, * installing/uninstalling packages, clearing user data, etc. * The default value is <code>false</code>. @@ -485,18 +504,19 @@ public class UserManager { /** * Specifies if a user is disallowed from enabling or disabling location providers. As a - * result, user is disallowed from turning on or off location. Device owner and profile owners - * can set this restriction and it only applies on the managed user. + * result, user is disallowed from turning on or off location. * - * <p>In a managed profile, location sharing is forced off when it's off on primary user, so - * user can still turn off location sharing on managed profile when the restriction is set by - * profile owner on managed profile. - * - * <p>This user restriction is different from {@link #DISALLOW_SHARE_LOCATION}, + * <p> + * In a managed profile, location sharing is forced off when it is turned off on the primary + * user or by the profile owner of an organization-owned managed profile on the parent profile. + * The user can still turn off location sharing on a managed profile when the restriction is + * set by the profile owner on a managed profile. + * <p> + * This user restriction is different from {@link #DISALLOW_SHARE_LOCATION}, * as the device owner or profile owner can still enable or disable location mode via * {@link DevicePolicyManager#setLocationEnabled} when this restriction is on. - * - * <p>The default value is <code>false</code>. + * <p> + * The default value is <code>false</code>. * * <p>Key for user restrictions. * <p>Type: Boolean @@ -510,7 +530,8 @@ public class UserManager { /** * Specifies if date, time and timezone configuring is disallowed. * - * <p>When restriction is set by device owners, it applies globally - i.e., it disables date, + * <p>When restriction is set by device owners or profile owners of organization-owned + * managed profiles on the parent profile, it applies globally - i.e., it disables date, * time and timezone setting on the entire device and all users will be affected. When it's set * by profile owners, it's only applied to the managed user. * <p>The default value is <code>false</code>. @@ -526,8 +547,9 @@ public class UserManager { /** * Specifies if a user is disallowed from configuring Tethering - * & portable hotspots. This can only be set by device owners and profile owners on the - * primary user. The default value is <code>false</code>. + * & portable hotspots. This can only be set by device owners, profile owners on the + * primary user or profile owners of organization-owned managed profiles on the parent profile. + * The default value is <code>false</code>. * <p>In Android 9.0 or higher, if tethering is enabled when this restriction is set, * tethering will be automatically turned off. * @@ -571,8 +593,8 @@ public class UserManager { /** * Specifies if a user is disallowed from adding new users. This can only be set by device - * owners and profile owners on the primary user. - * The default value is <code>false</code>. + * owners, profile owners on the primary user or profile owners of organization-owned managed + * profiles on the parent profile. The default value is <code>false</code>. * <p>This restriction has no effect on secondary users and managed profiles since only the * primary user can add other users. * @@ -621,7 +643,8 @@ public class UserManager { /** * Specifies if a user is disallowed from configuring cell - * broadcasts. This can only be set by device owners and profile owners on the primary user. + * broadcasts. This can only be set by device owners, profile owners on the primary user or + * profile owners of organization-owned managed profiles on the parent profile. * The default value is <code>false</code>. * <p>This restriction has no effect on secondary users and managed profiles since only the * primary user can configure cell broadcasts. @@ -636,7 +659,8 @@ public class UserManager { /** * Specifies if a user is disallowed from configuring mobile - * networks. This can only be set by device owners and profile owners on the primary user. + * networks. This can only be set by device owners, profile owners on the primary user or + * profile owners of organization-owned managed profiles on the parent profile. * The default value is <code>false</code>. * <p>This restriction has no effect on secondary users and managed profiles since only the * primary user can configure mobile networks. @@ -739,6 +763,10 @@ public class UserManager { /** * Specifies that the user is not allowed to send or receive * SMS messages. The default value is <code>false</code>. + * <p> + * Device owner and profile owner can set this restriction. When it is set by the + * profile owner of an organization-owned managed profile on the parent profile, + * it will disable SMS in the personal profile. * * <p>Key for user restrictions. * <p>Type: Boolean @@ -857,7 +885,8 @@ public class UserManager { /** * Specifies if the user is not allowed to reboot the device into safe boot mode. - * This can only be set by device owners and profile owners on the primary user. + * This can only be set by device owners, profile owners on the primary user or profile + * owners of organization-owned managed profiles on the parent profile. * The default value is <code>false</code>. * * <p>Key for user restrictions. @@ -896,6 +925,12 @@ public class UserManager { /** * Specifies if a user is not allowed to use the camera. + * <p> + * Device owner and profile owner can set this restriction. When the restriction is set by + * the device owner or the profile owner of an organization-owned managed profile on the + * parent profile, it is applied globally. + * <p> + * The default value is <code>false</code>. * * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) @@ -916,7 +951,8 @@ public class UserManager { /** * Specifies if a user is not allowed to use cellular data when roaming. This can only be set by - * device owners. The default value is <code>false</code>. + * device owners or profile owners of organization-owned managed profiles on the parent profile. + * The default value is <code>false</code>. * * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) @@ -1011,8 +1047,9 @@ public class UserManager { * Specifies if the contents of a user's screen is not allowed to be captured for artificial * intelligence purposes. * - * <p>Device owner and profile owner can set this restriction. When it is set by device owner, - * only the target user will be affected. + * <p>Device owner and profile owner can set this restriction. When it is set by the + * device owner or the profile owner of an organization-owned managed profile on the parent + * profile, only the target user will be affected. * * <p>The default value is <code>false</code>. * @@ -1026,8 +1063,9 @@ public class UserManager { * Specifies if the current user is able to receive content suggestions for selections based on * the contents of their screen. * - * <p>Device owner and profile owner can set this restriction. When it is set by device owner, - * only the target user will be affected. + * <p>Device owner and profile owner can set this restriction. When it is set by the + * device owner or the profile owner of an organization-owned managed profile on the parent + * profile, only the target user will be affected. * * <p>The default value is <code>false</code>. * @@ -1093,7 +1131,9 @@ public class UserManager { * * <p>The default value is <code>false</code>. * - * <p>This user restriction can only be applied by the Device Owner. + * <p>This user restriction can only be applied by the device owner or the profile owner + * of an organization-owned managed profile on the parent profile. + * * <p>Key for user restrictions. * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) @@ -2413,6 +2453,13 @@ public class UserManager { * by {@link #createUser(String, String, int)} or {@link #createGuest(Context, String)}), it * takes less time. * + * <p>This method completes the majority of work necessary for user creation: it + * creates user data, CE and DE encryption keys, app data directories, initializes the user and + * grants default permissions. When pre-created users become "real" users, only then are + * components notified of new user creation by firing user creation broadcasts. + * + * <p>All pre-created users are removed during system upgrade. + * * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} permission. * * @param userType the type of user, such as {@link UserManager#USER_TYPE_FULL_GUEST}. @@ -2424,6 +2471,7 @@ public class UserManager { * * @hide */ + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public @Nullable UserInfo preCreateUser(@NonNull String userType) { try { return mService.preCreateUser(userType); diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java index ccbb0f191f6f..ae75f3d0d7e6 100644 --- a/core/java/android/os/Vibrator.java +++ b/core/java/android/os/Vibrator.java @@ -155,7 +155,7 @@ public abstract class Vibrator { /** * Configure an always-on haptics effect. * - * @param id The board-specific always-on ID to configure. + * @param alwaysOnId The board-specific always-on ID to configure. * @param effect Vibration effect to assign to always-on id. Passing null will disable it. * @param attributes {@link AudioAttributes} corresponding to the vibration. For example, * specify {@link AudioAttributes#USAGE_ALARM} for alarm vibrations or @@ -164,8 +164,17 @@ public abstract class Vibrator { * @hide */ @RequiresPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON) - public boolean setAlwaysOnEffect(int id, @Nullable VibrationEffect effect, - @Nullable AudioAttributes attributes) { + public boolean setAlwaysOnEffect(int alwaysOnId, @Nullable VibrationEffect effect, + @Nullable AudioAttributes attributes) { + return setAlwaysOnEffect(Process.myUid(), mPackageName, alwaysOnId, effect, attributes); + } + + /** + * @hide + */ + @RequiresPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON) + public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, + @Nullable VibrationEffect effect, @Nullable AudioAttributes attributes) { Log.w(TAG, "Always-on effects aren't supported"); return false; } diff --git a/core/java/android/os/image/DynamicSystemClient.java b/core/java/android/os/image/DynamicSystemClient.java index 921f0f2ab1e2..5cb33615fe22 100644 --- a/core/java/android/os/image/DynamicSystemClient.java +++ b/core/java/android/os/image/DynamicSystemClient.java @@ -256,9 +256,13 @@ public class DynamicSystemClient { mService.send(msg); } catch (RemoteException e) { Slog.e(TAG, "Unable to get status from installation service"); - mExecutor.execute(() -> { + if (mExecutor != null) { + mExecutor.execute(() -> { + mListener.onStatusChanged(STATUS_UNKNOWN, CAUSE_ERROR_IPC, 0, e); + }); + } else { mListener.onStatusChanged(STATUS_UNKNOWN, CAUSE_ERROR_IPC, 0, e); - }); + } } } diff --git a/core/java/android/os/image/DynamicSystemManager.java b/core/java/android/os/image/DynamicSystemManager.java index 4c92c28c1bfa..cbf531c5730a 100644 --- a/core/java/android/os/image/DynamicSystemManager.java +++ b/core/java/android/os/image/DynamicSystemManager.java @@ -106,9 +106,9 @@ public class DynamicSystemManager { * @return true if the call succeeds */ @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM) - public boolean startInstallation() { + public boolean startInstallation(String dsuSlot) { try { - return mService.startInstallation(); + return mService.startInstallation(dsuSlot); } catch (RemoteException e) { throw new RuntimeException(e.toString()); } diff --git a/core/java/android/os/image/IDynamicSystemService.aidl b/core/java/android/os/image/IDynamicSystemService.aidl index 69cbab2c68ad..cc32f998d0c2 100644 --- a/core/java/android/os/image/IDynamicSystemService.aidl +++ b/core/java/android/os/image/IDynamicSystemService.aidl @@ -22,9 +22,10 @@ interface IDynamicSystemService { /** * Start DynamicSystem installation. + * @param dsuSlot Name used to identify this installation * @return true if the call succeeds */ - boolean startInstallation(); + boolean startInstallation(@utf8InCpp String dsuSlot); /** * Create a DSU partition. This call may take 60~90 seconds. The caller diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 258329203bdb..5a1ba7fe534c 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -160,6 +160,7 @@ public final class PermissionManager { * Grant default permissions to currently active LUI app * @param packageName The package name for the LUI app * @param user The user handle + * @param executor The executor for the callback * @param callback The callback provided by caller to be notified when grant completes * @hide */ @@ -181,6 +182,7 @@ public final class PermissionManager { * Revoke default permissions to currently active LUI app * @param packageNames The package names for the LUI apps * @param user The user handle + * @param executor The executor for the callback * @param callback The callback provided by caller to be notified when grant completes * @hide */ @@ -198,6 +200,72 @@ public final class PermissionManager { } } + /** + * Grant default permissions to currently active Ims services + * @param packageNames The package names for the Ims services + * @param user The user handle + * @param executor The executor for the callback + * @param callback The callback provided by caller to be notified when grant completes + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS) + public void grantDefaultPermissionsToEnabledImsServices( + @NonNull String[] packageNames, @NonNull UserHandle user, + @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) { + try { + mPermissionManager.grantDefaultPermissionsToEnabledImsServices( + packageNames, user.getIdentifier()); + executor.execute(() -> callback.accept(true)); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * Grant default permissions to currently enabled telephony data services + * @param packageNames The package name for the services + * @param user The user handle + * @param executor The executor for the callback + * @param callback The callback provided by caller to be notified when grant completes + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS) + public void grantDefaultPermissionsToEnabledTelephonyDataServices( + @NonNull String[] packageNames, @NonNull UserHandle user, + @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) { + try { + mPermissionManager.grantDefaultPermissionsToEnabledTelephonyDataServices( + packageNames, user.getIdentifier()); + executor.execute(() -> callback.accept(true)); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * Revoke default permissions to currently active telephony data services + * @param packageNames The package name for the services + * @param user The user handle + * @param executor The executor for the callback + * @param callback The callback provided by caller to be notified when revoke completes + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS) + public void revokeDefaultPermissionsFromDisabledTelephonyDataServices( + @NonNull String[] packageNames, @NonNull UserHandle user, + @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) { + try { + mPermissionManager.revokeDefaultPermissionsFromDisabledTelephonyDataServices( + packageNames, user.getIdentifier()); + executor.execute(() -> callback.accept(true)); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + private List<SplitPermissionInfo> splitPermissionInfoListToNonParcelableList( List<SplitPermissionInfoParcelable> parcelableList) { final int size = parcelableList.size(); diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index 6650cf23d611..53f46158238e 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -424,6 +424,7 @@ public final class DeviceConfig { * @hide */ @SystemApi + @TestApi @NonNull @RequiresPermission(READ_DEVICE_CONFIG) public static Properties getProperties(@NonNull String namespace, @NonNull String ... names) { @@ -593,6 +594,7 @@ public final class DeviceConfig { * @hide */ @SystemApi + @TestApi @RequiresPermission(WRITE_DEVICE_CONFIG) public static boolean setProperties(@NonNull Properties properties) throws BadConfigException { ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver(); @@ -817,6 +819,7 @@ public final class DeviceConfig { * @hide */ @SystemApi + @TestApi public static class BadConfigException extends Exception {} /** diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index ef8a2860cf4f..1453608c1518 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -363,7 +363,7 @@ public final class DocumentsContract { * <p> * Type: INTEGER (int) * - * @see #FLAG_DIR_BLOCKS_TREE + * @see #FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE * @see #FLAG_DIR_PREFERS_GRID * @see #FLAG_DIR_PREFERS_LAST_MODIFIED * @see #FLAG_DIR_SUPPORTS_CREATE @@ -567,7 +567,7 @@ public final class DocumentsContract { * @see Intent#ACTION_OPEN_DOCUMENT_TREE * @see #COLUMN_FLAGS */ - public static final int FLAG_DIR_BLOCKS_TREE = 1 << 15; + public static final int FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE = 1 << 15; } /** diff --git a/core/java/android/provider/SearchIndexablesContract.java b/core/java/android/provider/SearchIndexablesContract.java index 298628e7578b..8fc13b7316f0 100644 --- a/core/java/android/provider/SearchIndexablesContract.java +++ b/core/java/android/provider/SearchIndexablesContract.java @@ -98,16 +98,14 @@ public class SearchIndexablesContract { /** - * Dynamic indexable raw data names. - * - * @hide + * The raw data name of dynamic index. This is used to compose the index path of provider + * for dynamic index. */ public static final String DYNAMIC_INDEXABLES_RAW = "dynamic_indexables_raw"; /** - * ContentProvider path for dynamic indexable raw data. - * - * @hide + * ContentProvider path for dynamic index. This is used to get the raw data of dynamic index + * from provider. */ public static final String DYNAMIC_INDEXABLES_RAW_PATH = SETTINGS + "/" + DYNAMIC_INDEXABLES_RAW; diff --git a/core/java/android/provider/SearchIndexablesProvider.java b/core/java/android/provider/SearchIndexablesProvider.java index 68284b4895c3..f4d0cb4d43d3 100644 --- a/core/java/android/provider/SearchIndexablesProvider.java +++ b/core/java/android/provider/SearchIndexablesProvider.java @@ -204,11 +204,9 @@ public abstract class SearchIndexablesProvider extends ContentProvider { * @param projection list of {@link android.provider.SearchIndexablesContract.RawData} columns * to put into the cursor. If {@code null} all supported columns should be * included. - * - * @hide */ @Nullable - public Cursor queryDynamicRawData(String[] projection) { + public Cursor queryDynamicRawData(@Nullable String[] projection) { // By default no-op; return null; } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 548b1230d6e2..f6633201ec73 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -95,6 +95,7 @@ import java.util.Set; * The Settings provider contains global system-level device preferences. */ public final class Settings { + private static final boolean DEFAULT_OVERRIDEABLE_BY_RESTORE = false; // Intent actions for Settings @@ -523,7 +524,9 @@ public final class Settings { * Input: The Intent's data URI specifies bootstrapping information for authenticating and * provisioning the peer, and uses a "DPP" scheme. The URI should be attached to the intent * using {@link Intent#setData(Uri)}. The calling app can obtain a DPP URI in any - * way, e.g. by scanning a QR code or other out-of-band methods. + * way, e.g. by scanning a QR code or other out-of-band methods. The calling app may also + * attach the {@link #EXTRA_EASY_CONNECT_BAND_LIST} extra to provide information + * about the bands supported by the enrollee device. * <p> * Output: After calling {@link android.app.Activity#startActivityForResult}, the callback * {@code onActivityResult} will have resultCode {@link android.app.Activity#RESULT_OK} if @@ -595,17 +598,29 @@ public final class Settings { /** * Activity Extra: The Band List that the Enrollee supports. * <p> - * An extra returned on the result intent received when using the {@link - * #ACTION_PROCESS_WIFI_EASY_CONNECT_URI} intent to launch the Easy Connect Operation. This - * extra contains the bands the Enrollee supports, expressed as the Global Operating Class, - * see Table E-4 in IEEE Std 802.11-2016 Global operating classes. This value is populated only - * by remote R2 devices, and only for the following error codes: {@link - * android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK} - * {@link android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION} + * This extra contains the bands the Enrollee supports, expressed as the Global Operating + * Class, see Table E-4 in IEEE Std 802.11-2016 Global operating classes. It is used both as + * input, to configure the Easy Connect operation and as output of the operation. + * <p> + * As input: an optional extra to be attached to the + * {@link #ACTION_PROCESS_WIFI_EASY_CONNECT_URI}. If attached, it indicates the bands which + * the remote device (enrollee, device-to-be-configured) supports. The Settings operation + * may take this into account when presenting the user with list of networks configurations + * to be used. The calling app may obtain this information in any out-of-band method. The + * information should be attached as an array of raw integers - using the + * {@link Intent#putExtra(String, int[])}. + * <p> + * As output: an extra returned on the result intent received when using the + * {@link #ACTION_PROCESS_WIFI_EASY_CONNECT_URI} intent to launch the Easy Connect Operation + * . This value is populated only by remote R2 devices, and only for the following error + * codes: + * {@link android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK}, + * {@link android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION}, + * or * {@link android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION}. - * Therefore, always check if this extra is available using {@link Intent#hasExtra(String)}. If - * there is no error, i.e. if the operation returns {@link android.app.Activity#RESULT_OK}, then - * this extra is not attached to the result intent. + * Therefore, always check if this extra is available using {@link Intent#hasExtra(String)}. + * If there is no error, i.e. if the operation returns {@link android.app.Activity#RESULT_OK} + * , then this extra is not attached to the result intent. * <p> * Use the {@link Intent#getIntArrayExtra(String)} to obtain the list. */ @@ -2135,6 +2150,11 @@ public final class Settings { */ public static final String CALL_METHOD_FLAGS_KEY = "_flags"; + /** + * @hide - String argument extra to the fast-path call()-based requests + */ + public static final String CALL_METHOD_OVERRIDEABLE_BY_RESTORE_KEY = "_overrideable_by_restore"; + /** @hide - Private call() method to write to 'system' table */ public static final String CALL_METHOD_PUT_SYSTEM = "PUT_system"; @@ -2503,7 +2523,8 @@ public final class Settings { } public boolean putStringForUser(ContentResolver cr, String name, String value, - String tag, boolean makeDefault, final int userHandle) { + String tag, boolean makeDefault, final int userHandle, + boolean overrideableByRestore) { try { Bundle arg = new Bundle(); arg.putString(Settings.NameValueTable.VALUE, value); @@ -2514,6 +2535,9 @@ public final class Settings { if (makeDefault) { arg.putBoolean(CALL_METHOD_MAKE_DEFAULT_KEY, true); } + if (overrideableByRestore) { + arg.putBoolean(CALL_METHOD_OVERRIDEABLE_BY_RESTORE_KEY, true); + } IContentProvider cp = mProviderHolder.getProvider(cr); cp.call(cr.getPackageName(), cr.getFeatureId(), mProviderHolder.mUri.getAuthority(), mCallSetCommand, name, arg); @@ -2722,6 +2746,8 @@ public final class Settings { public ArrayMap<String, String> getStringsForPrefix(ContentResolver cr, String prefix, List<String> names) { + String namespace = prefix.substring(0, prefix.length() - 1); + DeviceConfig.enforceReadPermission(ActivityThread.currentApplication(), namespace); ArrayMap<String, String> keyValues = new ArrayMap<>(); int currentGeneration = -1; @@ -3064,10 +3090,36 @@ public final class Settings { return putStringForUser(resolver, name, value, resolver.getUserId()); } + /** + * Store a name/value pair into the database. Values written by this method will be + * overridden if a restore happens in the future. + * + * @param resolver to access the database with + * @param name to store + * @param value to associate with the name + * + * @return true if the value was set, false on database errors + * + * @hide + */ + @RequiresPermission(Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE) + @SystemApi + public static boolean putString(@NonNull ContentResolver resolver, + @NonNull String name, @Nullable String value, boolean overrideableByRestore) { + return putStringForUser(resolver, name, value, resolver.getUserId(), + overrideableByRestore); + } + /** @hide */ @UnsupportedAppUsage public static boolean putStringForUser(ContentResolver resolver, String name, String value, int userHandle) { + return putStringForUser(resolver, name, value, userHandle, + DEFAULT_OVERRIDEABLE_BY_RESTORE); + } + + private static boolean putStringForUser(ContentResolver resolver, String name, String value, + int userHandle, boolean overrideableByRestore) { if (MOVED_TO_SECURE.contains(name)) { Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System" + " to android.provider.Settings.Secure, value is unchanged."); @@ -3078,7 +3130,8 @@ public final class Settings { + " to android.provider.Settings.Global, value is unchanged."); return false; } - return sNameValueCache.putStringForUser(resolver, name, value, null, false, userHandle); + return sNameValueCache.putStringForUser(resolver, name, value, null, false, userHandle, + overrideableByRestore); } /** @@ -3402,7 +3455,7 @@ public final class Settings { // need to store the adjusted configuration as the initial settings. Settings.System.putStringForUser( cr, SYSTEM_LOCALES, outConfig.getLocales().toLanguageTags(), - userHandle); + userHandle, DEFAULT_OVERRIDEABLE_BY_RESTORE); } } } @@ -3435,7 +3488,8 @@ public final class Settings { int userHandle) { return Settings.System.putFloatForUser(cr, FONT_SCALE, config.fontScale, userHandle) && Settings.System.putStringForUser( - cr, SYSTEM_LOCALES, config.getLocales().toLanguageTags(), userHandle); + cr, SYSTEM_LOCALES, config.getLocales().toLanguageTags(), userHandle, + DEFAULT_OVERRIDEABLE_BY_RESTORE); } /** @hide */ @@ -5123,7 +5177,6 @@ public final class Settings { MOVED_TO_GLOBAL.add(Settings.Global.WIFI_P2P_DEVICE_NAME); MOVED_TO_GLOBAL.add(Settings.Global.WIFI_SAVED_STATE); MOVED_TO_GLOBAL.add(Settings.Global.WIFI_SUPPLICANT_SCAN_INTERVAL_MS); - MOVED_TO_GLOBAL.add(Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED); MOVED_TO_GLOBAL.add(Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED); MOVED_TO_GLOBAL.add(Settings.Global.WIFI_ENHANCED_AUTO_JOIN); MOVED_TO_GLOBAL.add(Settings.Global.WIFI_NETWORK_SHOW_RSSI); @@ -5238,6 +5291,24 @@ public final class Settings { } /** + * Store a name/value pair into the database. Values written by this method will be + * overridden if a restore happens in the future. + * + * @param resolver to access the database with + * @param name to store + * @param value to associate with the name + * @return true if the value was set, false on database errors + * + * @hide + */ + @RequiresPermission(Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE) + public static boolean putString(ContentResolver resolver, String name, + String value, boolean overrideableByRestore) { + return putStringForUser(resolver, name, value, /* tag */ null, /* makeDefault */ false, + resolver.getUserId(), overrideableByRestore); + } + + /** * Store a name/value pair into the database. * @param resolver to access the database with * @param name to store @@ -5252,22 +5323,23 @@ public final class Settings { @UnsupportedAppUsage public static boolean putStringForUser(ContentResolver resolver, String name, String value, int userHandle) { - return putStringForUser(resolver, name, value, null, false, userHandle); + return putStringForUser(resolver, name, value, null, false, userHandle, + DEFAULT_OVERRIDEABLE_BY_RESTORE); } /** @hide */ @UnsupportedAppUsage public static boolean putStringForUser(@NonNull ContentResolver resolver, @NonNull String name, @Nullable String value, @Nullable String tag, - boolean makeDefault, @UserIdInt int userHandle) { + boolean makeDefault, @UserIdInt int userHandle, boolean overrideableByRestore) { if (MOVED_TO_GLOBAL.contains(name)) { Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Secure" + " to android.provider.Settings.Global"); return Global.putStringForUser(resolver, name, value, - tag, makeDefault, userHandle); + tag, makeDefault, userHandle, DEFAULT_OVERRIDEABLE_BY_RESTORE); } return sNameValueCache.putStringForUser(resolver, name, value, tag, - makeDefault, userHandle); + makeDefault, userHandle, overrideableByRestore); } /** @@ -5316,7 +5388,7 @@ public final class Settings { @NonNull String name, @Nullable String value, @Nullable String tag, boolean makeDefault) { return putStringForUser(resolver, name, value, tag, makeDefault, - resolver.getUserId()); + resolver.getUserId(), DEFAULT_OVERRIDEABLE_BY_RESTORE); } /** @@ -6406,6 +6478,15 @@ public final class Settings { "accessibility_button_target_component"; /** + * The system class name of magnification controller which is a target to be toggled via + * accessibility shortcut or accessibility button. + * + * @hide + */ + public static final String ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER = + "com.android.server.accessibility.MagnificationController"; + + /** * If touch exploration is enabled. */ public static final String TOUCH_EXPLORATION_ENABLED = "touch_exploration_enabled"; @@ -8797,9 +8878,9 @@ public final class Settings { * added to both AIRPLANE_MODE_RADIOS and AIRPLANE_MODE_TOGGLEABLE_RADIOS, then Wifi * will be turned off when entering airplane mode, but the user will be able to reenable * Wifi in the Settings app. - * - * {@hide} + * @hide */ + @SystemApi public static final String AIRPLANE_MODE_TOGGLEABLE_RADIOS = "airplane_mode_toggleable_radios"; /** @@ -9971,24 +10052,17 @@ public final class Settings { * Setting to allow scans to be enabled even wifi is turned off for connectivity. * @hide */ + @SystemApi public static final String WIFI_SCAN_ALWAYS_AVAILABLE = "wifi_scan_always_enabled"; /** - * The interval in milliseconds at which wifi rtt ranging requests will be throttled when - * they are coming from the background. - * - * @hide - */ - public static final String WIFI_RTT_BACKGROUND_EXEC_GAP_MS = - "wifi_rtt_background_exec_gap_ms"; - - /** * Indicate whether factory reset request is pending. * * Type: int (0 for false, 1 for true) * @hide */ + @SystemApi public static final String WIFI_P2P_PENDING_FACTORY_RESET = "wifi_p2p_pending_factory_reset"; @@ -9998,6 +10072,7 @@ public final class Settings { * Type: int (0 for false, 1 for true) * @hide */ + @SystemApi public static final String SOFT_AP_TIMEOUT_ENABLED = "soft_ap_timeout_enabled"; /** @@ -10041,10 +10116,10 @@ public final class Settings { * enabled state. * @hide */ + @SystemApi public static final String NETWORK_RECOMMENDATIONS_ENABLED = "network_recommendations_enabled"; - /** * Which package name to use for network recommendations. If null, network recommendations * will neither be requested nor accepted. @@ -10069,17 +10144,6 @@ public final class Settings { public static final String USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package"; /** - * The number of milliseconds the {@link com.android.server.NetworkScoreService} - * will give a recommendation request to complete before returning a default response. - * - * Type: long - * @hide - * @deprecated to be removed - */ - public static final String NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS = - "network_recommendation_request_timeout_ms"; - - /** * The expiration time in milliseconds for the {@link android.net.WifiKey} request cache in * {@link com.android.server.wifi.RecommendedNetworkEvaluator}. * @@ -10097,6 +10161,7 @@ public final class Settings { * Type: int (0 for false, 1 for true) * @hide */ + @SystemApi public static final String WIFI_SCAN_THROTTLE_ENABLED = "wifi_scan_throttle_enabled"; /** @@ -10205,18 +10270,11 @@ public final class Settings { "wifi_watchdog_poor_network_test_enabled"; /** - * Setting to turn on suspend optimizations at screen off on Wi-Fi. Enabled by default and - * needs to be set to 0 to disable it. - * @hide - */ - public static final String WIFI_SUSPEND_OPTIMIZATIONS_ENABLED = - "wifi_suspend_optimizations_enabled"; - - /** * Setting to enable verbose logging in Wi-Fi; disabled by default, and setting to 1 * will enable it. In the future, additional values may be supported. * @hide */ + @SystemApi public static final String WIFI_VERBOSE_LOGGING_ENABLED = "wifi_verbose_logging_enabled"; @@ -10242,69 +10300,10 @@ public final class Settings { * Errors in the parameters will cause the entire setting to be ignored. * @hide */ + @SystemApi public static final String WIFI_SCORE_PARAMS = "wifi_score_params"; - /** - * Setting to enable logging WifiIsUnusableEvent in metrics - * which gets triggered when wifi becomes unusable. - * Disabled by default, and setting it to 1 will enable it. - * @hide - */ - public static final String WIFI_IS_UNUSABLE_EVENT_METRICS_ENABLED = - "wifi_is_unusable_event_metrics_enabled"; - - /** - * The minimum number of txBad the framework has to observe - * to trigger a wifi data stall. - * @hide - */ - public static final String WIFI_DATA_STALL_MIN_TX_BAD = - "wifi_data_stall_min_tx_bad"; - - /** - * The minimum number of txSuccess the framework has to observe - * to trigger a wifi data stall when rxSuccess is 0. - * @hide - */ - public static final String WIFI_DATA_STALL_MIN_TX_SUCCESS_WITHOUT_RX = - "wifi_data_stall_min_tx_success_without_rx"; - - /** - * Setting to enable logging Wifi LinkSpeedCounts in metrics. - * Disabled by default, and setting it to 1 will enable it. - * @hide - */ - public static final String WIFI_LINK_SPEED_METRICS_ENABLED = - "wifi_link_speed_metrics_enabled"; - - /** - * Setting to enable the PNO frequency culling optimization. - * Disabled by default, and setting it to 1 will enable it. - * The value is boolean (0 or 1). - * @hide - */ - public static final String WIFI_PNO_FREQUENCY_CULLING_ENABLED = - "wifi_pno_frequency_culling_enabled"; - - /** - * Setting to enable including recency information when determining pno network priorities. - * Disabled by default, and setting it to 1 will enable it. - * The value is boolean (0 or 1). - * @hide - */ - public static final String WIFI_PNO_RECENCY_SORTING_ENABLED = - "wifi_pno_recency_sorting_enabled"; - - /** - * Setting to enable the Wi-Fi link probing. - * Enabled by default, and setting it to 0 will disable it. - * The value is boolean (0 or 1). - * @hide - */ - public static final String WIFI_LINK_PROBING_ENABLED = - "wifi_link_probing_enabled"; - /** * The maximum number of times we will retry a connection to an access * point for which we have failed in acquiring an IP address from DHCP. @@ -10344,6 +10343,7 @@ public final class Settings { * The Wi-Fi peer-to-peer device name * @hide */ + @SystemApi public static final String WIFI_P2P_DEVICE_NAME = "wifi_p2p_device_name"; /** @@ -12999,7 +12999,29 @@ public final class Settings { */ public static boolean putString(ContentResolver resolver, String name, String value) { - return putStringForUser(resolver, name, value, null, false, resolver.getUserId()); + return putStringForUser(resolver, name, value, null, false, resolver.getUserId(), + DEFAULT_OVERRIDEABLE_BY_RESTORE); + } + + /** + * Store a name/value pair into the database. + * + * @param resolver to access the database with + * @param name to store + * @param value to associate with the name + * @param tag to associated with the setting. + * @param makeDefault whether to make the value the default one. + * @param overrideableByRestore whether restore can override this value + * @return true if the value was set, false on database errors + * + * @hide + */ + @RequiresPermission(Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE) + public static boolean putString(@NonNull ContentResolver resolver, + @NonNull String name, @Nullable String value, @Nullable String tag, + boolean makeDefault, boolean overrideableByRestore) { + return putStringForUser(resolver, name, value, tag, makeDefault, + resolver.getUserId(), overrideableByRestore); } /** @@ -13048,7 +13070,7 @@ public final class Settings { @NonNull String name, @Nullable String value, @Nullable String tag, boolean makeDefault) { return putStringForUser(resolver, name, value, tag, makeDefault, - resolver.getUserId()); + resolver.getUserId(), DEFAULT_OVERRIDEABLE_BY_RESTORE); } /** @@ -13110,13 +13132,14 @@ public final class Settings { @UnsupportedAppUsage public static boolean putStringForUser(ContentResolver resolver, String name, String value, int userHandle) { - return putStringForUser(resolver, name, value, null, false, userHandle); + return putStringForUser(resolver, name, value, null, false, userHandle, + DEFAULT_OVERRIDEABLE_BY_RESTORE); } /** @hide */ public static boolean putStringForUser(@NonNull ContentResolver resolver, @NonNull String name, @Nullable String value, @Nullable String tag, - boolean makeDefault, @UserIdInt int userHandle) { + boolean makeDefault, @UserIdInt int userHandle, boolean overrideableByRestore) { if (LOCAL_LOGV) { Log.v(TAG, "Global.putString(name=" + name + ", value=" + value + " for " + userHandle); @@ -13126,10 +13149,10 @@ public final class Settings { Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Global" + " to android.provider.Settings.Secure, value is unchanged."); return Secure.putStringForUser(resolver, name, value, tag, - makeDefault, userHandle); + makeDefault, userHandle, overrideableByRestore); } return sNameValueCache.putStringForUser(resolver, name, value, tag, - makeDefault, userHandle); + makeDefault, userHandle, overrideableByRestore); } /** @@ -13996,7 +14019,8 @@ public final class Settings { static boolean putString(@NonNull ContentResolver resolver, @NonNull String namespace, @NonNull String name, @Nullable String value, boolean makeDefault) { return sNameValueCache.putStringForUser(resolver, createCompositeName(namespace, name), - value, null, makeDefault, resolver.getUserId()); + value, null, makeDefault, resolver.getUserId(), + DEFAULT_OVERRIDEABLE_BY_RESTORE); } /** diff --git a/core/java/android/service/watchdog/ExplicitHealthCheckService.java b/core/java/android/service/watchdog/ExplicitHealthCheckService.java index 619c507c2bd3..995014374721 100644 --- a/core/java/android/service/watchdog/ExplicitHealthCheckService.java +++ b/core/java/android/service/watchdog/ExplicitHealthCheckService.java @@ -24,6 +24,7 @@ import android.annotation.SdkConstant; import android.annotation.SystemApi; import android.app.Service; import android.content.Intent; +import android.content.pm.PackageManager; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -48,7 +49,8 @@ import java.util.concurrent.TimeUnit; * <p>To extend this class, you must declare the service in your manifest file with the * {@link android.Manifest.permission#BIND_EXPLICIT_HEALTH_CHECK_SERVICE} permission, * and include an intent filter with the {@link #SERVICE_INTERFACE} action. In adddition, - * your implementation must live in {@link PackageManger#SYSTEM_SHARED_LIBRARY_SERVICES}. + * your implementation must live in + * {@link PackageManager#getServicesSystemSharedLibraryPackageName()}. * For example:</p> * <pre> * <service android:name=".FooExplicitHealthCheckService" diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java index e08a06abad45..a4fe6aa0c875 100644 --- a/core/java/android/telephony/PhoneStateListener.java +++ b/core/java/android/telephony/PhoneStateListener.java @@ -384,6 +384,17 @@ public class PhoneStateListener { @RequiresPermission(Manifest.permission.READ_PHONE_STATE) public static final int LISTEN_REGISTRATION_FAILURE = 0x40000000; + /** + * Listen for Barring Information for the current registered / camped cell. + * + * <p>Requires permission {@link android.Manifest.permission#READ_PHONE_STATE} or the calling + * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}). + * + * @see #onBarringInfoChanged() + */ + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + public static final int LISTEN_BARRING_INFO = 0x80000000; + /* * Subscription used to listen to the phone state changes * @hide @@ -974,7 +985,21 @@ public class PhoneStateListener { * TS 24.301 9.9.4.4. Integer.MAX_VALUE if this value is unused. */ public void onRegistrationFailed(@NonNull CellIdentity cellIdentity, @NonNull String chosenPlmn, - @NetworkRegistrationInfo.Domain int domain, int causeCode, int additionalCauseCode) { + int domain, int causeCode, int additionalCauseCode) { + // default implementation empty + } + + /** + * Report updated barring information for the current camped/registered cell. + * + * <p>Barring info is provided for all services applicable to the current camped/registered + * cell, for the registered PLMN and current access class/access category. + * + * @param barringInfo for all services on the current cell. + * + * @see android.telephony.BarringInfo + */ + public void onBarringInfoChanged(@NonNull BarringInfo barringInfo) { // default implementation empty } @@ -1252,7 +1277,7 @@ public class PhoneStateListener { } public void onRegistrationFailed(@NonNull CellIdentity cellIdentity, - @NonNull String chosenPlmn, @NetworkRegistrationInfo.Domain int domain, + @NonNull String chosenPlmn, int domain, int causeCode, int additionalCauseCode) { PhoneStateListener psl = mPhoneStateListenerWeakRef.get(); if (psl == null) return; @@ -1262,6 +1287,14 @@ public class PhoneStateListener { cellIdentity, chosenPlmn, domain, causeCode, additionalCauseCode))); // default implementation empty } + + public void onBarringInfoChanged(BarringInfo barringInfo) { + PhoneStateListener psl = mPhoneStateListenerWeakRef.get(); + if (psl == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute(() -> psl.onBarringInfoChanged(barringInfo))); + } } diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java index 9387a2c79c6c..e25826c25e35 100644 --- a/core/java/android/telephony/TelephonyRegistryManager.java +++ b/core/java/android/telephony/TelephonyRegistryManager.java @@ -701,11 +701,28 @@ public class TelephonyRegistryManager { */ public void notifyRegistrationFailed(int slotIndex, int subId, @NonNull CellIdentity cellIdentity, @NonNull String chosenPlmn, - @NetworkRegistrationInfo.Domain int domain, int causeCode, int additionalCauseCode) { + int domain, int causeCode, int additionalCauseCode) { try { sRegistry.notifyRegistrationFailed(slotIndex, subId, cellIdentity, chosenPlmn, domain, causeCode, additionalCauseCode); } catch (RemoteException ex) { } } + + /** + * Notify {@link BarringInfo} has changed for a specific subscription. + * + * @param slotIndex for the phone object that got updated barring info. + * @param subId for which the BarringInfo changed. + * @param barringInfo updated BarringInfo. + */ + public void notifyBarringInfoChanged( + int slotIndex, int subId, @NonNull BarringInfo barringInfo) { + try { + sRegistry.notifyBarringInfoChanged(slotIndex, subId, barringInfo); + } catch (RemoteException ex) { + // system server crash + } + } + } diff --git a/core/java/android/timezone/CountryTimeZones.java b/core/java/android/timezone/CountryTimeZones.java new file mode 100644 index 000000000000..ada59d6d7d55 --- /dev/null +++ b/core/java/android/timezone/CountryTimeZones.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.timezone; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.icu.util.TimeZone; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * Information about a country's time zones. + * + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public final class CountryTimeZones { + + /** + * A mapping to a time zone ID with some associated metadata. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final class TimeZoneMapping { + + private libcore.timezone.CountryTimeZones.TimeZoneMapping mDelegate; + + TimeZoneMapping(libcore.timezone.CountryTimeZones.TimeZoneMapping delegate) { + this.mDelegate = Objects.requireNonNull(delegate); + } + + /** + * Returns the ID for this mapping. See also {@link #getTimeZone()} which handles when the + * ID is unrecognized. + */ + @NonNull + public String getTimeZoneId() { + return mDelegate.timeZoneId; + } + + /** + * Returns a {@link TimeZone} object for this mapping, or {@code null} if the ID is + * unrecognized. + */ + @Nullable + public TimeZone getTimeZone() { + return mDelegate.getTimeZone(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TimeZoneMapping that = (TimeZoneMapping) o; + return this.mDelegate.equals(that.mDelegate); + } + + @Override + public int hashCode() { + return this.mDelegate.hashCode(); + } + + @Override + public String toString() { + return mDelegate.toString(); + } + } + + /** + * The result of lookup up a time zone using offset information (and possibly more). + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final class OffsetResult { + + private final TimeZone mTimeZone; + private final boolean mIsOnlyMatch; + + /** Creates an instance with the supplied information. */ + public OffsetResult(@NonNull TimeZone timeZone, boolean isOnlyMatch) { + mTimeZone = Objects.requireNonNull(timeZone); + mIsOnlyMatch = isOnlyMatch; + } + + /** + * Returns a time zone that matches the supplied criteria. + */ + @NonNull + public TimeZone getTimeZone() { + return mTimeZone; + } + + /** + * Returns {@code true} if there is only one matching time zone for the supplied criteria. + */ + public boolean isOnlyMatch() { + return mIsOnlyMatch; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + OffsetResult that = (OffsetResult) o; + return mIsOnlyMatch == that.mIsOnlyMatch + && mTimeZone.getID().equals(that.mTimeZone.getID()); + } + + @Override + public int hashCode() { + return Objects.hash(mTimeZone, mIsOnlyMatch); + } + + @Override + public String toString() { + return "OffsetResult{" + + "mTimeZone=" + mTimeZone + + ", mIsOnlyMatch=" + mIsOnlyMatch + + '}'; + } + } + + @NonNull + private final libcore.timezone.CountryTimeZones mDelegate; + + CountryTimeZones(libcore.timezone.CountryTimeZones delegate) { + mDelegate = delegate; + } + + /** + * Returns true if the ISO code for the country is a match for the one specified. + */ + public boolean isForCountryCode(@NonNull String countryIso) { + return mDelegate.isForCountryCode(countryIso); + } + + /** + * Returns the default time zone ID for the country. Can return {@code null} in cases when no + * data is available or the time zone ID was not recognized. + */ + @Nullable + public String getDefaultTimeZoneId() { + return mDelegate.getDefaultTimeZoneId(); + } + + /** + * Returns the default time zone for the country. Can return {@code null} in cases when no data + * is available or the time zone ID was not recognized. + */ + @Nullable + public TimeZone getDefaultTimeZone() { + return mDelegate.getDefaultTimeZone(); + } + + /** + * Qualifier for a country's default time zone. {@code true} indicates whether the default + * would be a good choice <em>generally</em> when there's no other information available. + */ + public boolean isDefaultTimeZoneBoosted() { + return mDelegate.getDefaultTimeZoneBoost(); + } + + /** + * Returns true if the country has at least one zone that is the same as UTC at the given time. + */ + public boolean hasUtcZone(long whenMillis) { + return mDelegate.hasUtcZone(whenMillis); + } + + /** + * Returns a time zone for the country, if there is one, that matches the desired properties. If + * there are multiple matches and the {@code bias} is one of them then it is returned, otherwise + * an arbitrary match is returned based on the {@link #getEffectiveTimeZoneMappingsAt(long)} + * ordering. + * + * @param totalOffsetMillis the offset from UTC at {@code whenMillis} + * @param isDst the Daylight Savings Time state at {@code whenMillis}. {@code true} means DST, + * {@code false} means not DST, {@code null} means unknown + * @param dstOffsetMillis the part of {@code totalOffsetMillis} contributed by DST, only used if + * {@code isDst} is {@code true}. The value can be {@code null} if the DST offset is + * unknown + * @param whenMillis the UTC time to match against + * @param bias the time zone to prefer, can be {@code null} + */ + @Nullable + public OffsetResult lookupByOffsetWithBias(int totalOffsetMillis, @Nullable Boolean isDst, + @SuppressLint("AutoBoxing") @Nullable Integer dstOffsetMillis, long whenMillis, + @Nullable TimeZone bias) { + libcore.timezone.CountryTimeZones.OffsetResult delegateOffsetResult = + mDelegate.lookupByOffsetWithBias( + totalOffsetMillis, isDst, dstOffsetMillis, whenMillis, bias); + return delegateOffsetResult == null ? null : + new OffsetResult(delegateOffsetResult.mTimeZone, delegateOffsetResult.mOneMatch); + } + + /** + * Returns an immutable, ordered list of time zone mappings for the country in an undefined but + * "priority" order, filtered so that only "effective" time zone IDs are returned. An + * "effective" time zone is one that differs from another time zone used in the country after + * {@code whenMillis}. The list can be empty if there were no zones configured or the configured + * zone IDs were not recognized. + */ + @NonNull + public List<TimeZoneMapping> getEffectiveTimeZoneMappingsAt(long whenMillis) { + List<libcore.timezone.CountryTimeZones.TimeZoneMapping> delegateList = + mDelegate.getEffectiveTimeZoneMappingsAt(whenMillis); + + List<TimeZoneMapping> toReturn = new ArrayList<>(delegateList.size()); + for (libcore.timezone.CountryTimeZones.TimeZoneMapping delegateMapping : delegateList) { + toReturn.add(new TimeZoneMapping(delegateMapping)); + } + return Collections.unmodifiableList(toReturn); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CountryTimeZones that = (CountryTimeZones) o; + return mDelegate.equals(that.mDelegate); + } + + @Override + public int hashCode() { + return Objects.hash(mDelegate); + } + + @Override + public String toString() { + return mDelegate.toString(); + } +} diff --git a/core/java/android/timezone/TelephonyLookup.java b/core/java/android/timezone/TelephonyLookup.java new file mode 100644 index 000000000000..39dbe85cb485 --- /dev/null +++ b/core/java/android/timezone/TelephonyLookup.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.timezone; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; + +import com.android.internal.annotations.GuardedBy; + +import java.util.Objects; + +/** + * A class that can find time zone-related information about telephony networks. + * + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public class TelephonyLookup { + + private static Object sLock = new Object(); + @GuardedBy("sLock") + private static TelephonyLookup sInstance; + + @NonNull + private final libcore.timezone.TelephonyLookup mDelegate; + + /** + * Obtains an instance for use when resolving telephony time zone information. This method never + * returns {@code null}. + */ + @NonNull + public static TelephonyLookup getInstance() { + synchronized (sLock) { + if (sInstance == null) { + sInstance = new TelephonyLookup(libcore.timezone.TelephonyLookup.getInstance()); + } + return sInstance; + } + } + + private TelephonyLookup(@NonNull libcore.timezone.TelephonyLookup delegate) { + mDelegate = Objects.requireNonNull(delegate); + } + + /** + * Returns an object capable of querying telephony network information. This method can return + * {@code null} in the event of an error while reading the underlying data files. + */ + @Nullable + public TelephonyNetworkFinder getTelephonyNetworkFinder() { + libcore.timezone.TelephonyNetworkFinder telephonyNetworkFinderDelegate = + mDelegate.getTelephonyNetworkFinder(); + return telephonyNetworkFinderDelegate != null + ? new TelephonyNetworkFinder(telephonyNetworkFinderDelegate) : null; + } +} diff --git a/core/java/android/timezone/TelephonyNetwork.java b/core/java/android/timezone/TelephonyNetwork.java new file mode 100644 index 000000000000..ae39fbddfa1c --- /dev/null +++ b/core/java/android/timezone/TelephonyNetwork.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.timezone; + +import android.annotation.NonNull; +import android.annotation.SystemApi; + +import java.util.Objects; + +/** + * Information about a telephony network. + * + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public class TelephonyNetwork { + + @NonNull + private final libcore.timezone.TelephonyNetwork mDelegate; + + TelephonyNetwork(@NonNull libcore.timezone.TelephonyNetwork delegate) { + mDelegate = Objects.requireNonNull(delegate); + } + + /** + * Returns the Mobile Country Code of the network. + */ + @NonNull + public String getMcc() { + return mDelegate.getMcc(); + } + + /** + * Returns the Mobile Network Code of the network. + */ + @NonNull + public String getMnc() { + return mDelegate.getMnc(); + } + + /** + * Returns the country in which the network operates as an ISO 3166 alpha-2 (lower case). + */ + @NonNull + public String getCountryIsoCode() { + return mDelegate.getCountryIsoCode(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TelephonyNetwork that = (TelephonyNetwork) o; + return mDelegate.equals(that.mDelegate); + } + + @Override + public int hashCode() { + return Objects.hash(mDelegate); + } + + @Override + public String toString() { + return "TelephonyNetwork{" + + "mDelegate=" + mDelegate + + '}'; + } +} diff --git a/core/java/android/timezone/TelephonyNetworkFinder.java b/core/java/android/timezone/TelephonyNetworkFinder.java new file mode 100644 index 000000000000..a81a516c4b33 --- /dev/null +++ b/core/java/android/timezone/TelephonyNetworkFinder.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.timezone; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; + +import java.util.Objects; + +/** + * A class that can find telephony networks loaded via {@link TelephonyLookup}. + * + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public class TelephonyNetworkFinder { + + @NonNull + private final libcore.timezone.TelephonyNetworkFinder mDelegate; + + TelephonyNetworkFinder(libcore.timezone.TelephonyNetworkFinder delegate) { + mDelegate = Objects.requireNonNull(delegate); + } + + /** + * Returns information held about a specific MCC + MNC combination. It is expected for this + * method to return {@code null}. Only known, unusual networks will typically have information + * returned, e.g. if they operate in countries other than the one suggested by their MCC. + */ + @Nullable + public TelephonyNetwork findNetworkByMccMnc(@NonNull String mcc, @NonNull String mnc) { + Objects.requireNonNull(mcc); + Objects.requireNonNull(mnc); + + libcore.timezone.TelephonyNetwork telephonyNetworkDelegate = + mDelegate.findNetworkByMccMnc(mcc, mnc); + return telephonyNetworkDelegate != null + ? new TelephonyNetwork(telephonyNetworkDelegate) : null; + } +} diff --git a/core/java/android/timezone/TimeZoneFinder.java b/core/java/android/timezone/TimeZoneFinder.java new file mode 100644 index 000000000000..15dfe62bb789 --- /dev/null +++ b/core/java/android/timezone/TimeZoneFinder.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.timezone; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; + +import com.android.internal.annotations.GuardedBy; + +/** + * A class that can be used to find time zones. + * + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public final class TimeZoneFinder { + + private static Object sLock = new Object(); + @GuardedBy("sLock") + private static TimeZoneFinder sInstance; + + private final libcore.timezone.TimeZoneFinder mDelegate; + + private TimeZoneFinder(libcore.timezone.TimeZoneFinder delegate) { + mDelegate = delegate; + } + + /** + * Obtains an instance for use when resolving telephony time zone information. This method never + * returns {@code null}. + */ + @NonNull + public static TimeZoneFinder getInstance() { + synchronized (sLock) { + if (sInstance == null) { + sInstance = new TimeZoneFinder(libcore.timezone.TimeZoneFinder.getInstance()); + } + } + return sInstance; + } + + /** + * Returns a {@link CountryTimeZones} object associated with the specified country code. + * Caching is handled as needed. If the country code is not recognized or there is an error + * during lookup this method can return null. + */ + @Nullable + public CountryTimeZones lookupCountryTimeZones(@NonNull String countryIso) { + libcore.timezone.CountryTimeZones delegate = mDelegate.lookupCountryTimeZones(countryIso); + return delegate == null ? null : new CountryTimeZones(delegate); + } +} diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 1b2db36c1335..eb4af1c2a979 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -42,6 +42,8 @@ public class FeatureFlagUtils { public static final String DYNAMIC_SYSTEM = "settings_dynamic_system"; public static final String SETTINGS_WIFITRACKER2 = "settings_wifitracker2"; public static final String SETTINGS_FUSE_FLAG = "settings_fuse"; + public static final String NOTIF_CONVO_BYPASS_SHORTCUT_REQ = + "settings_notif_convo_bypass_shortcut_req"; private static final Map<String, String> DEFAULT_FLAGS; @@ -57,9 +59,9 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put("settings_wifi_details_datausage_header", "false"); DEFAULT_FLAGS.put("settings_skip_direction_mutable", "true"); DEFAULT_FLAGS.put(SETTINGS_WIFITRACKER2, "false"); - DEFAULT_FLAGS.put("settings_work_profile", "true"); DEFAULT_FLAGS.put("settings_controller_loading_enhancement", "false"); DEFAULT_FLAGS.put("settings_conditionals", "false"); + DEFAULT_FLAGS.put(NOTIF_CONVO_BYPASS_SHORTCUT_REQ, "true"); } /** diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java index f324113da925..9921bf0bc527 100644 --- a/core/java/android/util/Log.java +++ b/core/java/android/util/Log.java @@ -19,6 +19,7 @@ package android.util; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.os.DeadSystemException; @@ -400,7 +401,7 @@ public final class Log { * @param message The message you would like logged. * @hide */ - // @SystemApi(client= SystemApi.Client.MODULE_LIBRARIES) // TODO Uncomment once http://ag/9956147 is in. + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static int logToRadioBuffer(@Level int priority, @Nullable String tag, @Nullable String message) { return println_native(LOG_ID_RADIO, priority, tag, message); diff --git a/core/java/android/util/StatsLog.java b/core/java/android/util/StatsLog.java index 952d7cbcd119..8635340397b4 100644 --- a/core/java/android/util/StatsLog.java +++ b/core/java/android/util/StatsLog.java @@ -254,6 +254,7 @@ public final class StatsLog extends StatsLogInternal { * @param statsEvent The StatsEvent object containing the encoded buffer of data to write. * @hide */ + @SystemApi public static void write(@NonNull final StatsEvent statsEvent) { writeImpl(statsEvent.getBytes(), statsEvent.getNumBytes(), statsEvent.getAtomId()); statsEvent.release(); diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 4368115917e5..178b3c0cb94e 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -19,6 +19,7 @@ package android.view; import static android.Manifest.permission.CONFIGURE_DISPLAY_COLOR_MODE; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; @@ -1010,6 +1011,9 @@ public final class Display { * @return Supported WCG color spaces. * @hide */ + @SuppressLint("VisiblySynchronized") + @NonNull + @TestApi public @ColorMode ColorSpace[] getSupportedWideColorGamut() { synchronized (this) { final ColorSpace[] defaultColorSpaces = new ColorSpace[0]; diff --git a/core/java/android/view/ImeFocusController.java b/core/java/android/view/ImeFocusController.java new file mode 100644 index 000000000000..5c494c17669a --- /dev/null +++ b/core/java/android/view/ImeFocusController.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.annotation.AnyThread; +import android.annotation.NonNull; +import android.annotation.UiThread; +import android.util.Log; +import android.view.inputmethod.InputMethodManager; + +import com.android.internal.inputmethod.InputMethodDebug; +import com.android.internal.inputmethod.StartInputFlags; +import com.android.internal.inputmethod.StartInputReason; + +/** + * Responsible for IME focus handling inside {@link ViewRootImpl}. + * @hide + */ +public final class ImeFocusController { + private static final boolean DEBUG = false; + private static final String TAG = "ImeFocusController"; + + private final ViewRootImpl mViewRootImpl; + private boolean mHasImeFocus = false; + private View mServedView; + private View mNextServedView; + + @UiThread + ImeFocusController(@NonNull ViewRootImpl viewRootImpl) { + mViewRootImpl = viewRootImpl; + } + + private InputMethodManagerDelegate getImmDelegate() { + return mViewRootImpl.mContext.getSystemService(InputMethodManager.class).getDelegate(); + } + + @UiThread + void onTraversal(boolean hasWindowFocus, WindowManager.LayoutParams windowAttribute) { + final boolean hasImeFocus = updateImeFocusable(windowAttribute, false /* force */); + if (!hasWindowFocus || isInLocalFocusMode(windowAttribute)) { + return; + } + if (hasImeFocus == mHasImeFocus) { + return; + } + mHasImeFocus = hasImeFocus; + if (mHasImeFocus) { + onPreWindowFocus(true /* hasWindowFocus */, windowAttribute); + onPostWindowFocus(mViewRootImpl.mView.findFocus(), true /* hasWindowFocus */, + windowAttribute); + } + } + + @UiThread + void onPreWindowFocus(boolean hasWindowFocus, WindowManager.LayoutParams windowAttribute) { + if (!mHasImeFocus || isInLocalFocusMode(windowAttribute)) { + return; + } + if (hasWindowFocus) { + getImmDelegate().setCurrentRootView(mViewRootImpl); + } + } + + @UiThread + boolean updateImeFocusable(WindowManager.LayoutParams windowAttribute, boolean force) { + final boolean hasImeFocus = WindowManager.LayoutParams.mayUseInputMethod( + windowAttribute.flags); + if (force) { + mHasImeFocus = hasImeFocus; + } + return hasImeFocus; + } + + @UiThread + void onPostWindowFocus(View focusedView, boolean hasWindowFocus, + WindowManager.LayoutParams windowAttribute) { + if (!hasWindowFocus || !mHasImeFocus || isInLocalFocusMode(windowAttribute)) { + return; + } + if (DEBUG) { + Log.v(TAG, "onWindowFocus: " + focusedView + + " softInputMode=" + InputMethodDebug.softInputModeToString( + windowAttribute.softInputMode)); + } + + boolean forceFocus = false; + if (getImmDelegate().isRestartOnNextWindowFocus(true /* reset */)) { + if (DEBUG) Log.v(TAG, "Restarting due to isRestartOnNextWindowFocus as true"); + forceFocus = true; + } + // Update mNextServedView when focusedView changed. + final View viewForWindowFocus = focusedView != null ? focusedView : mViewRootImpl.mView; + onViewFocusChanged(viewForWindowFocus, true); + + getImmDelegate().startInputAsyncOnWindowFocusGain(viewForWindowFocus, + windowAttribute.softInputMode, windowAttribute.flags, forceFocus); + } + + public boolean checkFocus(boolean forceNewFocus, boolean startInput) { + if (!getImmDelegate().isCurrentRootView(mViewRootImpl) + || (mServedView == mNextServedView && !forceNewFocus)) { + return false; + } + if (DEBUG) Log.v(TAG, "checkFocus: view=" + mServedView + + " next=" + mNextServedView + + " force=" + forceNewFocus + + " package=" + + (mServedView != null ? mServedView.getContext().getPackageName() : "<none>")); + + // Close the connection when no next served view coming. + if (mNextServedView == null) { + getImmDelegate().finishInput(); + getImmDelegate().closeCurrentIme(); + return false; + } + mServedView = mNextServedView; + getImmDelegate().finishComposingText(); + + if (startInput) { + getImmDelegate().startInput(StartInputReason.CHECK_FOCUS, null, 0, 0, 0); + } + return true; + } + + @UiThread + void onViewFocusChanged(View view, boolean hasFocus) { + if (view == null || view.isTemporarilyDetached()) { + return; + } + if (!getImmDelegate().isCurrentRootView(view.getViewRootImpl())) { + return; + } + if (mServedView == view || !view.hasImeFocus() || !view.hasWindowFocus()) { + return; + } + mNextServedView = hasFocus ? view : null; + mViewRootImpl.dispatchCheckFocus(); + } + + @UiThread + void onViewDetachedFromWindow(View view) { + if (!getImmDelegate().isCurrentRootView(view.getViewRootImpl())) { + return; + } + if (mServedView == view) { + mNextServedView = null; + mViewRootImpl.dispatchCheckFocus(); + } + } + + @UiThread + void onWindowDismissed() { + if (!getImmDelegate().isCurrentRootView(mViewRootImpl)) { + return; + } + if (mServedView != null) { + getImmDelegate().finishInput(); + } + getImmDelegate().setCurrentRootView(null); + mHasImeFocus = false; + } + + /** + * @param windowAttribute {@link WindowManager.LayoutParams} to be checked. + * @return Whether the window is in local focus mode or not. + */ + @AnyThread + private static boolean isInLocalFocusMode(WindowManager.LayoutParams windowAttribute) { + return (windowAttribute.flags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0; + } + + int onProcessImeInputStage(Object token, InputEvent event, + WindowManager.LayoutParams windowAttribute, + InputMethodManager.FinishedInputEventCallback callback) { + if (!mHasImeFocus || isInLocalFocusMode(windowAttribute)) { + return InputMethodManager.DISPATCH_NOT_HANDLED; + } + final InputMethodManager imm = + mViewRootImpl.mContext.getSystemService(InputMethodManager.class); + if (imm == null) { + return InputMethodManager.DISPATCH_NOT_HANDLED; + } + return imm.dispatchInputEvent(event, token, callback, mViewRootImpl.mHandler); + } + + /** + * A delegate implementing some basic {@link InputMethodManager} APIs. + * @hide + */ + public interface InputMethodManagerDelegate { + boolean startInput(@StartInputReason int startInputReason, View focusedView, + @StartInputFlags int startInputFlags, + @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags); + void startInputAsyncOnWindowFocusGain(View rootView, + @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags, + boolean forceNewFocus); + void finishInput(); + void closeCurrentIme(); + void finishComposingText(); + void setCurrentRootView(ViewRootImpl rootView); + boolean isCurrentRootView(ViewRootImpl rootView); + boolean isRestartOnNextWindowFocus(boolean reset); + } + + public View getServedView() { + return mServedView; + } + + public View getNextServedView() { + return mNextServedView; + } + + public void setServedView(View view) { + mServedView = view; + } + + public void setNextServedView(View view) { + mNextServedView = view; + } +} diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java index c67ff6ea0111..7986ceb988db 100644 --- a/core/java/android/view/InputEventReceiver.java +++ b/core/java/android/view/InputEventReceiver.java @@ -128,6 +128,19 @@ public abstract class InputEventReceiver { } /** + * Called when a focus event is received. + * + * @param hasFocus if true, the window associated with this input channel has just received + * focus + * if false, the window associated with this input channel has just lost focus + * @param inTouchMode if true, the device is in touch mode + * if false, the device is not in touch mode + */ + // Called from native code. + public void onFocusEvent(boolean hasFocus, boolean inTouchMode) { + } + + /** * Called when a batched input event is pending. * * The batched input event will continue to accumulate additional movement @@ -213,8 +226,13 @@ public abstract class InputEventReceiver { onBatchedInputEventPending(); } - public static interface Factory { - public InputEventReceiver createInputEventReceiver( - InputChannel inputChannel, Looper looper); + /** + * Factory for InputEventReceiver + */ + public interface Factory { + /** + * Create a new InputReceiver for a given inputChannel + */ + InputEventReceiver createInputEventReceiver(InputChannel inputChannel, Looper looper); } } diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index 6589e75c7bc2..69d01050801f 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -34,6 +34,7 @@ import android.util.SparseIntArray; import android.util.SparseSetArray; import android.view.InsetsController.LayoutInsetsDuringAnimation; import android.view.InsetsState.InternalInsetsSide; +import android.view.InsetsState.InternalInsetsType; import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsAnimationCallback.AnimationBounds; @@ -92,6 +93,7 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll mController = controller; mInitialInsetsState = new InsetsState(state, true /* copySources */); mCurrentInsets = getInsetsFromState(mInitialInsetsState, frame, null /* typeSideMap */); + mPendingInsets = mCurrentInsets; mHiddenInsets = calculateInsets(mInitialInsetsState, frame, controls, false /* shown */, null /* typeSideMap */); mShownInsets = calculateInsets(mInitialInsetsState, frame, controls, true /* shown */, @@ -131,6 +133,10 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll return mTypes; } + boolean controlsInternalType(@InternalInsetsType int type) { + return InsetsState.toInternalType(mTypes).contains(type); + } + @Override public void setInsetsAndAlpha(Insets insets, float alpha, float fraction) { if (mFinished) { diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 775490c757d4..e2739c469e5f 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -28,6 +28,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.graphics.Insets; import android.graphics.Rect; +import android.net.InvalidPacketException.ErrorCode; import android.os.RemoteException; import android.util.ArraySet; import android.util.Log; @@ -61,15 +62,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private static final int ANIMATION_DURATION_SHOW_MS = 275; private static final int ANIMATION_DURATION_HIDE_MS = 340; - private static final int DIRECTION_NONE = 0; - private static final int DIRECTION_SHOW = 1; - private static final int DIRECTION_HIDE = 2; static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f); - @IntDef ({DIRECTION_NONE, DIRECTION_SHOW, DIRECTION_HIDE}) - private @interface AnimationDirection{} - /** * Layout mode during insets animation: The views should be laid out as if the changing inset * types are fully shown. Before starting the animation, {@link View#onApplyWindowInsets} will @@ -101,6 +96,28 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @interface LayoutInsetsDuringAnimation { } + /** Not running an animation. */ + @VisibleForTesting + public static final int ANIMATION_TYPE_NONE = -1; + + /** Running animation will show insets */ + @VisibleForTesting + public static final int ANIMATION_TYPE_SHOW = 0; + + /** Running animation will hide insets */ + @VisibleForTesting + public static final int ANIMATION_TYPE_HIDE = 1; + + /** Running animation is controlled by user via {@link #controlWindowInsetsAnimation} */ + @VisibleForTesting + public static final int ANIMATION_TYPE_USER = 2; + + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = {ANIMATION_TYPE_NONE, ANIMATION_TYPE_SHOW, ANIMATION_TYPE_HIDE, + ANIMATION_TYPE_USER}) + @interface AnimationType { + } + /** * Translation animation evaluator. */ @@ -145,7 +162,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation public void onReady(WindowInsetsAnimationController controller, int types) { mController = controller; - mAnimationDirection = mShow ? DIRECTION_SHOW : DIRECTION_HIDE; mAnimator = ObjectAnimator.ofObject( controller, new InsetsProperty(), @@ -176,7 +192,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } private void onAnimationFinish() { - mAnimationDirection = DIRECTION_NONE; mController.finish(mShow); } @@ -193,6 +208,20 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } } + /** + * Represents a running animation + */ + private static class RunningAnimation { + + RunningAnimation(InsetsAnimationControlImpl control, int type) { + this.control = control; + this.type = type; + } + + final InsetsAnimationControlImpl control; + final @AnimationType int type; + } + private final String TAG = "InsetsControllerImpl"; private final InsetsState mState = new InsetsState(); @@ -203,7 +232,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private final ViewRootImpl mViewRoot; private final SparseArray<InsetsSourceControl> mTmpControlArray = new SparseArray<>(); - private final ArrayList<InsetsAnimationControlImpl> mAnimationControls = new ArrayList<>(); + private final ArrayList<RunningAnimation> mRunningAnimations = new ArrayList<>(); private final ArrayList<InsetsAnimationControlImpl> mTmpFinishedControls = new ArrayList<>(); private WindowInsets mLastInsets; @@ -213,7 +242,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private final Rect mLastLegacyContentInsets = new Rect(); private final Rect mLastLegacyStableInsets = new Rect(); - private @AnimationDirection int mAnimationDirection; private int mPendingTypesToShow; @@ -226,7 +254,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation mViewRoot = viewRoot; mAnimCallback = () -> { mAnimCallbackScheduled = false; - if (mAnimationControls.isEmpty()) { + if (mRunningAnimations.isEmpty()) { return; } if (mViewRoot.mView == null) { @@ -236,9 +264,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation mTmpFinishedControls.clear(); InsetsState state = new InsetsState(mState, true /* copySources */); - for (int i = mAnimationControls.size() - 1; i >= 0; i--) { - InsetsAnimationControlImpl control = mAnimationControls.get(i); - if (mAnimationControls.get(i).applyChangeInsets(state)) { + for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { + InsetsAnimationControlImpl control = mRunningAnimations.get(i).control; + if (control.applyChangeInsets(state)) { mTmpFinishedControls.add(control); } } @@ -349,18 +377,13 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation int typesReady = 0; final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); for (int i = internalTypes.size() - 1; i >= 0; i--) { - InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i)); - if (mAnimationDirection == DIRECTION_HIDE) { - // Only one animator (with multiple InsetsType) can run at a time. - // previous one should be cancelled for simplicity. - cancelExistingAnimation(); - } else if (consumer.isRequestedVisible() - && (mAnimationDirection == DIRECTION_NONE - || mAnimationDirection == DIRECTION_HIDE)) { + @InternalInsetsType int internalType = internalTypes.valueAt(i); + @AnimationType int animationType = getAnimationType(internalType); + InsetsSourceConsumer consumer = getSourceConsumer(internalType); + if (mState.getSource(internalType).isVisible() && animationType == ANIMATION_TYPE_NONE + || animationType == ANIMATION_TYPE_SHOW) { // no-op: already shown or animating in (because window visibility is // applied before starting animation). - // TODO: When we have more than one types: handle specific case when - // show animation is going on, but the current type is not becoming visible. continue; } typesReady |= InsetsState.toPublicType(consumer.getType()); @@ -377,12 +400,11 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation int typesReady = 0; final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); for (int i = internalTypes.size() - 1; i >= 0; i--) { - InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i)); - if (mAnimationDirection == DIRECTION_SHOW) { - cancelExistingAnimation(); - } else if (!consumer.isRequestedVisible() - && (mAnimationDirection == DIRECTION_NONE - || mAnimationDirection == DIRECTION_HIDE)) { + @InternalInsetsType int internalType = internalTypes.valueAt(i); + @AnimationType int animationType = getAnimationType(internalType); + InsetsSourceConsumer consumer = getSourceConsumer(internalType); + if (!mState.getSource(internalType).isVisible() && animationType == ANIMATION_TYPE_NONE + || animationType == ANIMATION_TYPE_HIDE) { // no-op: already hidden or animating out. continue; } @@ -394,11 +416,13 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @Override public void controlWindowInsetsAnimation(@InsetsType int types, long durationMs, WindowInsetsAnimationControlListener listener) { - controlWindowInsetsAnimation(types, listener, false /* fromIme */, durationMs); + controlWindowInsetsAnimation(types, listener, false /* fromIme */, durationMs, + ANIMATION_TYPE_USER); } private void controlWindowInsetsAnimation(@InsetsType int types, - WindowInsetsAnimationControlListener listener, boolean fromIme, long durationMs) { + WindowInsetsAnimationControlListener listener, boolean fromIme, long durationMs, + @AnimationType int animationType) { // If the frame of our window doesn't span the entire display, the control API makes very // little sense, as we don't deal with negative insets. So just cancel immediately. if (!mState.getDisplayFrame().equals(mFrame)) { @@ -406,12 +430,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation return; } controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs, false /* fade */, - getLayoutInsetsDuringAnimationMode(types)); + animationType, getLayoutInsetsDuringAnimationMode(types)); } private void controlAnimationUnchecked(@InsetsType int types, WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme, - long durationMs, boolean fade, + long durationMs, boolean fade, @AnimationType int animationType, @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) { if (types == 0) { // nothing to animate. @@ -444,7 +468,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(controls, frame, mState, listener, typesReady, this, durationMs, fade, layoutInsetsDuringAnimation); - mAnimationControls.add(controller); + mRunningAnimations.add(new RunningAnimation(controller, animationType)); } /** @@ -523,10 +547,10 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } private void cancelExistingControllers(@InsetsType int types) { - for (int i = mAnimationControls.size() - 1; i >= 0; i--) { - InsetsAnimationControlImpl control = mAnimationControls.get(i); + for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { + InsetsAnimationControlImpl control = mRunningAnimations.get(i).control; if ((control.getTypes() & types) != 0) { - cancelAnimation(control); + cancelAnimation(control, true /* invokeCallback */); } } } @@ -534,7 +558,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @VisibleForTesting @Override public void notifyFinished(InsetsAnimationControlImpl controller, boolean shown) { - mAnimationControls.remove(controller); + cancelAnimation(controller, false /* invokeCallback */); if (shown) { showDirectly(controller.getTypes()); } else { @@ -554,17 +578,24 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } void notifyControlRevoked(InsetsSourceConsumer consumer) { - for (int i = mAnimationControls.size() - 1; i >= 0; i--) { - InsetsAnimationControlImpl control = mAnimationControls.get(i); + for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { + InsetsAnimationControlImpl control = mRunningAnimations.get(i).control; if ((control.getTypes() & toPublicType(consumer.getType())) != 0) { - cancelAnimation(control); + cancelAnimation(control, true /* invokeCallback */); } } } - private void cancelAnimation(InsetsAnimationControlImpl control) { - control.onCancelled(); - mAnimationControls.remove(control); + private void cancelAnimation(InsetsAnimationControlImpl control, boolean invokeCallback) { + if (invokeCallback) { + control.onCancelled(); + } + for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { + if (mRunningAnimations.get(i).control == control) { + mRunningAnimations.remove(i); + break; + } + } } private void applyLocalVisibilityOverride() { @@ -622,8 +653,15 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } } - boolean isAnimating() { - return mAnimationDirection != DIRECTION_NONE; + @VisibleForTesting + public @AnimationType int getAnimationType(@InternalInsetsType int type) { + for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { + InsetsAnimationControlImpl control = mRunningAnimations.get(i).control; + if (control.controlsInternalType(type)) { + return mRunningAnimations.get(i).type; + } + } + return ANIMATION_TYPE_NONE; } private InsetsSourceConsumer createConsumerOfType(int type) { @@ -665,8 +703,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation // and hidden state insets are correct. controlAnimationUnchecked( types, listener, mState.getDisplayFrame(), fromIme, listener.getDurationMs(), - true /* fade */, show - ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN + true /* fade */, show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE, + show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN); } diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index b2a5d915c2a6..8a1b45a3a411 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -16,6 +16,8 @@ package android.view; +import static android.view.InsetsController.ANIMATION_TYPE_NONE; + import android.annotation.IntDef; import android.annotation.Nullable; import android.view.InsetsState.InternalInsetsType; @@ -172,7 +174,7 @@ public class InsetsSourceConsumer { private void applyHiddenToControl() { if (mSourceControl == null || mSourceControl.getLeash() == null - || mController.isAnimating()) { + || mController.getAnimationType(mType) != ANIMATION_TYPE_NONE) { return; } diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index ff8455ab0915..cc4278bdd2b6 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -122,6 +122,8 @@ public final class SurfaceControl implements Parcelable { private static native void nativeSetColor(long transactionObj, long nativeObject, float[] color); private static native void nativeSetFlags(long transactionObj, long nativeObject, int flags, int mask); + private static native void nativeSetFrameRateSelectionPriority(long transactionObj, + long nativeObject, int priority); private static native void nativeSetWindowCrop(long transactionObj, long nativeObject, int l, int t, int r, int b); private static native void nativeSetCornerRadius(long transactionObj, long nativeObject, @@ -2245,6 +2247,19 @@ public final class SurfaceControl implements Parcelable { } /** + * This information is passed to SurfaceFlinger to decide which window should have a + * priority when deciding about the refresh rate of the display. All windows have the + * lowest priority by default. + * @hide + */ + @NonNull + public Transaction setFrameRateSelectionPriority(@NonNull SurfaceControl sc, int priority) { + sc.checkNotReleased(); + nativeSetFrameRateSelectionPriority(mNativeObject, sc.mNativeObject, priority); + return this; + } + + /** * Request that a given surface and it's sub-tree be shown. * * @param sc The surface to show. diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 17825444a524..0de1a4f038ff 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -42,6 +42,7 @@ import android.os.SystemClock; import android.util.AttributeSet; import android.util.Log; import android.view.SurfaceControl.Transaction; +import android.view.accessibility.AccessibilityNodeInfo; import com.android.internal.view.SurfaceCallbackHelper; @@ -201,6 +202,9 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall private SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction(); private int mParentSurfaceGenerationId; + // The token of embedded windowless view hierarchy. + private IBinder mEmbeddedViewHierarchy; + public SurfaceView(Context context) { this(context, null); } @@ -1531,4 +1535,27 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall if (viewRoot == null) return; viewRoot.setUseBLASTSyncTransaction(); } + + /** + * Add the token of embedded view hierarchy. Set {@code null} to clear the embedded view + * hierarchy. + * + * @param token IBinder token. + * @hide + */ + public void setEmbeddedViewHierarchy(IBinder token) { + mEmbeddedViewHierarchy = token; + } + + /** @hide */ + @Override + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); + if (mEmbeddedViewHierarchy == null) { + return; + } + // Add a leashed child when this SurfaceView embeds another view hierarchy. Getting this + // leashed child would return the root node in the embedded hierarchy + info.addChild(mEmbeddedViewHierarchy); + } } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 13d609b16541..562ed0eb8e70 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -130,7 +130,6 @@ import android.view.contentcapture.ContentCaptureManager; import android.view.contentcapture.ContentCaptureSession; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; -import android.view.inputmethod.InputMethodManager; import android.view.inspector.InspectableProperty; import android.view.inspector.InspectableProperty.EnumEntry; import android.view.inspector.InspectableProperty.FlagEntry; @@ -7942,12 +7941,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (isPressed()) { setPressed(false); } - if (mAttachInfo != null && mAttachInfo.mHasWindowFocus) { - notifyFocusChangeToInputMethodManager(false /* hasFocus */); + if (hasWindowFocus()) { + notifyFocusChangeToImeFocusController(false /* hasFocus */); } onFocusLost(); - } else if (mAttachInfo != null && mAttachInfo.mHasWindowFocus) { - notifyFocusChangeToInputMethodManager(true /* hasFocus */); + } else if (hasWindowFocus()) { + notifyFocusChangeToImeFocusController(true /* hasFocus */); } invalidate(true); @@ -7964,23 +7963,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Notify {@link InputMethodManager} about the focus change of the {@link View}. - * - * <p>Does nothing when {@link InputMethodManager} is not available.</p> + * Notify {@link ImeFocusController} about the focus change of the {@link View}. * * @param hasFocus {@code true} when the {@link View} is being focused. */ - private void notifyFocusChangeToInputMethodManager(boolean hasFocus) { - final InputMethodManager imm = - getContext().getSystemService(InputMethodManager.class); - if (imm == null) { + private void notifyFocusChangeToImeFocusController(boolean hasFocus) { + if (mAttachInfo == null) { return; } - if (hasFocus) { - imm.focusIn(this); - } else { - imm.focusOut(this); - } + mAttachInfo.mViewRootImpl.getImeFocusController().onViewFocusChanged(this, hasFocus); } /** @hide */ @@ -13918,7 +13909,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags3 &= ~PFLAG3_TEMPORARY_DETACH; onFinishTemporaryDetach(); if (hasWindowFocus() && hasFocus()) { - notifyFocusChangeToInputMethodManager(true /* hasFocus */); + notifyFocusChangeToImeFocusController(true /* hasFocus */); } notifyEnterOrExitForAutoFillIfNeeded(true); notifyAppearedOrDisappearedForContentCaptureIfNeeded(true); @@ -14326,13 +14317,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; if ((mPrivateFlags & PFLAG_FOCUSED) != 0) { - notifyFocusChangeToInputMethodManager(false /* hasFocus */); + notifyFocusChangeToImeFocusController(false /* hasFocus */); } removeLongPressCallback(); removeTapCallback(); onFocusLost(); } else if ((mPrivateFlags & PFLAG_FOCUSED) != 0) { - notifyFocusChangeToInputMethodManager(true /* hasFocus */); + notifyFocusChangeToImeFocusController(true /* hasFocus */); } refreshDrawableState(); @@ -14349,6 +14340,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * @return {@code true} if this view is in a window that currently has IME focusable state. + * @hide + */ + public boolean hasImeFocus() { + return mAttachInfo != null && mAttachInfo.mHasImeFocus; + } + + /** * Dispatch a view visibility change down the view hierarchy. * ViewGroups should override to route to their children. * @param changedView The view whose visibility changed. Could be 'this' or @@ -19644,7 +19643,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, rebuildOutline(); if (isFocused()) { - notifyFocusChangeToInputMethodManager(true /* hasFocus */); + notifyFocusChangeToImeFocusController(true /* hasFocus */); } } @@ -20227,9 +20226,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, onDetachedFromWindow(); onDetachedFromWindowInternal(); - InputMethodManager imm = getContext().getSystemService(InputMethodManager.class); - if (imm != null) { - imm.onViewDetachedFromWindow(this); + if (info != null) { + info.mViewRootImpl.getImeFocusController().onViewDetachedFromWindow(this); } ListenerInfo li = mListenerInfo; @@ -28565,6 +28563,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, boolean mHasWindowFocus; /** + * Indicates whether the view's window has IME focused. + */ + boolean mHasImeFocus; + + /** * The current visibility of the window. */ int mWindowVisibility; diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index ca8ba4ca1b8a..17b945b71773 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -453,7 +453,6 @@ public final class ViewRootImpl implements ViewParent, boolean mReportNextDraw; boolean mFullRedrawNeeded; boolean mNewSurfaceNeeded; - boolean mLastWasImTarget; boolean mForceNextWindowRelayout; CountDownLatch mWindowDrawCountDown; @@ -619,6 +618,16 @@ public final class ViewRootImpl implements ViewParent, InputEventConsistencyVerifier.isInstrumentationEnabled() ? new InputEventConsistencyVerifier(this, 0) : null; + private final ImeFocusController mImeFocusController; + + /** + * @return {@link ImeFocusController} for this instance. + */ + @NonNull + public ImeFocusController getImeFocusController() { + return mImeFocusController; + } + private final InsetsController mInsetsController = new InsetsController(this); private final GestureExclusionTracker mGestureExclusionTracker = new GestureExclusionTracker(); @@ -704,6 +713,7 @@ public final class ViewRootImpl implements ViewParent, } loadSystemProperties(); + mImeFocusController = new ImeFocusController(this); } public static void addFirstDrawHandler(Runnable callback) { @@ -826,6 +836,10 @@ public final class ViewRootImpl implements ViewParent, if (mWindowAttributes.packageName == null) { mWindowAttributes.packageName = mBasePackageName; } + if (WindowManagerGlobal.USE_BLAST_ADAPTER) { + mWindowAttributes.privateFlags |= + WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST; + } attrs = mWindowAttributes; setTag(); @@ -1050,11 +1064,6 @@ public final class ViewRootImpl implements ViewParent, } } - /** Whether the window is in local focus mode or not */ - private boolean isInLocalFocusMode() { - return (mWindowAttributes.flags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0; - } - @UnsupportedAppUsage public int getWindowFlags() { return mWindowAttributes.flags; @@ -1308,7 +1317,7 @@ public final class ViewRootImpl implements ViewParent, mWindowAttributes.privateFlags |= compatibleWindowFlag; if (WindowManagerGlobal.USE_BLAST_ADAPTER) { - mWindowAttributes.privateFlags = + mWindowAttributes.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST; } @@ -2888,19 +2897,7 @@ public final class ViewRootImpl implements ViewParent, mViewVisibility = viewVisibility; mHadWindowFocus = hasWindowFocus; - if (hasWindowFocus && !isInLocalFocusMode()) { - final boolean imTarget = WindowManager.LayoutParams - .mayUseInputMethod(mWindowAttributes.flags); - if (imTarget != mLastWasImTarget) { - mLastWasImTarget = imTarget; - InputMethodManager imm = mContext.getSystemService(InputMethodManager.class); - if (imm != null && imTarget) { - imm.onPreWindowFocus(mView, hasWindowFocus); - imm.onPostWindowFocus(mView, mView.findFocus(), - mWindowAttributes.softInputMode, mWindowAttributes.flags); - } - } - } + mImeFocusController.onTraversal(hasWindowFocus, mWindowAttributes); // Remember if we must report the next draw. if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) { @@ -3068,14 +3065,10 @@ public final class ViewRootImpl implements ViewParent, } mAttachInfo.mHasWindowFocus = hasWindowFocus; + mAttachInfo.mHasImeFocus = mImeFocusController.updateImeFocusable( + mWindowAttributes, true /* force */); + mImeFocusController.onPreWindowFocus(hasWindowFocus, mWindowAttributes); - mLastWasImTarget = WindowManager.LayoutParams - .mayUseInputMethod(mWindowAttributes.flags); - - InputMethodManager imm = mContext.getSystemService(InputMethodManager.class); - if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) { - imm.onPreWindowFocus(mView, hasWindowFocus); - } if (mView != null) { mAttachInfo.mKeyDispatchState.reset(); mView.dispatchWindowFocusChanged(hasWindowFocus); @@ -3087,11 +3080,10 @@ public final class ViewRootImpl implements ViewParent, // Note: must be done after the focus change callbacks, // so all of the view state is set up correctly. + mImeFocusController.onPostWindowFocus(mView.findFocus(), hasWindowFocus, + mWindowAttributes); + if (hasWindowFocus) { - if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) { - imm.onPostWindowFocus(mView, mView.findFocus(), - mWindowAttributes.softInputMode, mWindowAttributes.flags); - } // Clear the forward bit. We can just do this directly, since // the window manager doesn't care about it. mWindowAttributes.softInputMode &= @@ -4887,10 +4879,7 @@ public final class ViewRootImpl implements ViewParent, enqueueInputEvent(event, null, 0, true); } break; case MSG_CHECK_FOCUS: { - InputMethodManager imm = mContext.getSystemService(InputMethodManager.class); - if (imm != null) { - imm.checkFocus(); - } + getImeFocusController().checkFocus(false, true); } break; case MSG_CLOSE_SYSTEM_DIALOGS: { if (mView != null) { @@ -5454,23 +5443,20 @@ public final class ViewRootImpl implements ViewParent, @Override protected int onProcess(QueuedInputEvent q) { - if (mLastWasImTarget && !isInLocalFocusMode()) { - InputMethodManager imm = mContext.getSystemService(InputMethodManager.class); - if (imm != null) { - final InputEvent event = q.mEvent; - if (DEBUG_IMF) Log.v(mTag, "Sending input event to IME: " + event); - int result = imm.dispatchInputEvent(event, q, this, mHandler); - if (result == InputMethodManager.DISPATCH_HANDLED) { - return FINISH_HANDLED; - } else if (result == InputMethodManager.DISPATCH_NOT_HANDLED) { - // The IME could not handle it, so skip along to the next InputStage - return FORWARD; - } else { - return DEFER; // callback will be invoked later - } - } + final int result = mImeFocusController.onProcessImeInputStage( + q, q.mEvent, mWindowAttributes, this); + switch (result) { + case InputMethodManager.DISPATCH_IN_PROGRESS: + // callback will be invoked later + return DEFER; + case InputMethodManager.DISPATCH_NOT_HANDLED: + // The IME could not handle it, so skip along to the next InputStage + return FORWARD; + case InputMethodManager.DISPATCH_HANDLED: + return FINISH_HANDLED; + default: + throw new IllegalStateException("Unexpected result=" + result); } - return FORWARD; } @Override @@ -8040,6 +8026,11 @@ public final class ViewRootImpl implements ViewParent, } @Override + public void onFocusEvent(boolean hasFocus, boolean inTouchMode) { + windowFocusChanged(hasFocus, inTouchMode); + } + + @Override public void dispose() { unscheduleConsumeBatchedInput(); super.dispose(); diff --git a/core/java/android/view/WindowContainerTransaction.java b/core/java/android/view/WindowContainerTransaction.java index 253794f70ef2..c55caa3213d8 100644 --- a/core/java/android/view/WindowContainerTransaction.java +++ b/core/java/android/view/WindowContainerTransaction.java @@ -74,6 +74,18 @@ public class WindowContainerTransaction implements Parcelable { return this; } + /** + * Sets whether a container or any of its children can be focusable. When {@code false}, no + * child can be focused; however, when {@code true}, it is still possible for children to be + * non-focusable due to WM policy. + */ + public WindowContainerTransaction setFocusable(IWindowContainer container, boolean focusable) { + Change chg = getOrCreateChange(container.asBinder()); + chg.mFocusable = focusable; + chg.mChangeMask |= Change.CHANGE_FOCUSABLE; + return this; + } + public Map<IBinder, Change> getChanges() { return mChanges; } @@ -112,7 +124,11 @@ public class WindowContainerTransaction implements Parcelable { * @hide */ public static class Change implements Parcelable { + public static final int CHANGE_FOCUSABLE = 1; + private final Configuration mConfiguration = new Configuration(); + private boolean mFocusable = true; + private int mChangeMask = 0; private @ActivityInfo.Config int mConfigSetMask = 0; private @WindowConfiguration.WindowConfig int mWindowSetMask = 0; @@ -123,6 +139,8 @@ public class WindowContainerTransaction implements Parcelable { protected Change(Parcel in) { mConfiguration.readFromParcel(in); + mFocusable = in.readBoolean(); + mChangeMask = in.readInt(); mConfigSetMask = in.readInt(); mWindowSetMask = in.readInt(); mSchedulePipCallback = (in.readInt() != 0); @@ -136,6 +154,18 @@ public class WindowContainerTransaction implements Parcelable { return mConfiguration; } + /** Gets the requested focusable value */ + public boolean getFocusable() { + if ((mChangeMask & CHANGE_FOCUSABLE) == 0) { + throw new RuntimeException("Focusable not set. check CHANGE_FOCUSABLE first"); + } + return mFocusable; + } + + public int getChangeMask() { + return mChangeMask; + } + @ActivityInfo.Config public int getConfigSetMask() { return mConfigSetMask; @@ -170,6 +200,9 @@ public class WindowContainerTransaction implements Parcelable { if (changesSss) { sb.append("ssw:" + mConfiguration.smallestScreenWidthDp + ","); } + if ((mChangeMask & CHANGE_FOCUSABLE) != 0) { + sb.append("focusable:" + mFocusable + ","); + } sb.append("}"); return sb.toString(); } @@ -177,6 +210,8 @@ public class WindowContainerTransaction implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { mConfiguration.writeToParcel(dest, flags); + dest.writeBoolean(mFocusable); + dest.writeInt(mChangeMask); dest.writeInt(mConfigSetMask); dest.writeInt(mWindowSetMask); diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index 7d5564e1c8be..ccfbd7e5c1dc 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -487,11 +487,8 @@ public final class WindowManagerGlobal { ViewRootImpl root = mRoots.get(index); View view = root.getView(); - if (view != null) { - InputMethodManager imm = view.getContext().getSystemService(InputMethodManager.class); - if (imm != null) { - imm.windowDismissed(mViews.get(index).getWindowToken()); - } + if (root != null) { + root.getImeFocusController().onWindowDismissed(); } boolean deferred = root.die(immediate); if (view != null) { diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index 914ff1871d69..b9f08ada3152 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -17,10 +17,14 @@ package android.view.accessibility; import android.accessibilityservice.IAccessibilityServiceConnection; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; +import android.graphics.Bitmap; import android.os.Binder; import android.os.Build; import android.os.Bundle; +import android.os.IBinder; import android.os.Message; import android.os.Process; import android.os.RemoteException; @@ -337,6 +341,48 @@ public final class AccessibilityInteractionClient return emptyWindows; } + + /** + * Finds an {@link AccessibilityNodeInfo} by accessibility id and given leash token instead of + * window id. This method is used to find the leashed node on the embedded view hierarchy. + * + * @param connectionId The id of a connection for interacting with the system. + * @param leashToken The token of the embedded hierarchy. + * @param accessibilityNodeId A unique view id or virtual descendant id from + * where to start the search. Use + * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID} + * to start from the root. + * @param bypassCache Whether to bypass the cache while looking for the node. + * @param prefetchFlags flags to guide prefetching. + * @param arguments Optional action arguments. + * @return An {@link AccessibilityNodeInfo} if found, null otherwise. + */ + public @Nullable AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId( + int connectionId, @NonNull IBinder leashToken, long accessibilityNodeId, + boolean bypassCache, int prefetchFlags, Bundle arguments) { + if (leashToken == null) { + return null; + } + int windowId = -1; + try { + IAccessibilityServiceConnection connection = getConnection(connectionId); + if (connection != null) { + windowId = connection.getWindowIdForLeashToken(leashToken); + } else { + if (DEBUG) { + Log.w(LOG_TAG, "No connection for connection id: " + connectionId); + } + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error while calling remote getWindowIdForLeashToken", re); + } + if (windowId == -1) { + return null; + } + return findAccessibilityNodeInfoByAccessibilityId(connectionId, windowId, + accessibilityNodeId, bypassCache, prefetchFlags, arguments); + } + /** * Finds an {@link AccessibilityNodeInfo} by accessibility id. * @@ -783,6 +829,31 @@ public final class AccessibilityInteractionClient } /** + * Takes the screenshot of the specified display and returns it by bitmap format. + * + * @param connectionId The id of a connection for interacting with the system. + * @param displayId The logic display id, use {@link Display#DEFAULT_DISPLAY} for + * default display. + * @return The screenshot bitmap on success, null otherwise. + */ + public Bitmap takeScreenshot(int connectionId, int displayId) { + Bitmap screenShot = null; + try { + IAccessibilityServiceConnection connection = getConnection(connectionId); + if (connection != null) { + screenShot = connection.takeScreenshot(displayId); + } else { + if (DEBUG) { + Log.w(LOG_TAG, "No connection for connection id: " + connectionId); + } + } + } catch (RemoteException re) { + Log.w(LOG_TAG, "Error while calling remote takeScreenshot", re); + } + return screenShot; + } + + /** * Clears the result state. */ private void clearResultLocked() { diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index 2e5a4b57da18..3dfeffbf9e6a 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -1195,6 +1195,19 @@ public final class AccessibilityManager { @TestApi @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY) public void performAccessibilityShortcut() { + performAccessibilityShortcut(null); + } + + /** + * Perform the accessibility shortcut for the given target which is assigned to the shortcut. + * + * @param targetName The flattened {@link ComponentName} string or the class name of a system + * class implementing a supported accessibility feature, or {@code null} if there's no + * specified target. + * @hide + */ + @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY) + public void performAccessibilityShortcut(@Nullable String targetName) { final IAccessibilityManager service; synchronized (mLock) { service = getServiceLocked(); @@ -1203,7 +1216,7 @@ public final class AccessibilityManager { } } try { - service.performAccessibilityShortcut(); + service.performAccessibilityShortcut(targetName); } catch (RemoteException re) { Log.e(LOG_TAG, "Error performing accessibility shortcut. ", re); } @@ -1270,7 +1283,22 @@ public final class AccessibilityManager { * @param displayId The logical display id. * @hide */ + @RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE) public void notifyAccessibilityButtonClicked(int displayId) { + notifyAccessibilityButtonClicked(displayId, null); + } + + /** + * Perform the accessibility button for the given target which is assigned to the button. + * + * @param displayId displayId The logical display id. + * @param targetName The flattened {@link ComponentName} string or the class name of a system + * class implementing a supported accessibility feature, or {@code null} if there's no + * specified target. + * @hide + */ + @RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE) + public void notifyAccessibilityButtonClicked(int displayId, @Nullable String targetName) { final IAccessibilityManager service; synchronized (mLock) { service = getServiceLocked(); @@ -1279,7 +1307,7 @@ public final class AccessibilityManager { } } try { - service.notifyAccessibilityButtonClicked(displayId); + service.notifyAccessibilityButtonClicked(displayId, targetName); } catch (RemoteException re) { Log.e(LOG_TAG, "Error while dispatching accessibility button click", re); } diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 92aa7d523da0..184f3302ae8d 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -31,6 +31,7 @@ import android.graphics.Rect; import android.graphics.Region; import android.os.Build; import android.os.Bundle; +import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.text.InputType; @@ -110,6 +111,9 @@ public class AccessibilityNodeInfo implements Parcelable { public static final int ROOT_ITEM_ID = Integer.MAX_VALUE - 1; /** @hide */ + public static final int LEASHED_ITEM_ID = Integer.MAX_VALUE - 2; + + /** @hide */ public static final long UNDEFINED_NODE_ID = makeNodeId(UNDEFINED_ITEM_ID, UNDEFINED_ITEM_ID); /** @hide */ @@ -117,6 +121,10 @@ public class AccessibilityNodeInfo implements Parcelable { AccessibilityNodeProvider.HOST_VIEW_ID); /** @hide */ + public static final long LEASHED_NODE_ID = makeNodeId(LEASHED_ITEM_ID, + AccessibilityNodeProvider.HOST_VIEW_ID); + + /** @hide */ public static final int FLAG_PREFETCH_PREDECESSORS = 0x00000001; /** @hide */ @@ -788,6 +796,10 @@ public class AccessibilityNodeInfo implements Parcelable { private TouchDelegateInfo mTouchDelegateInfo; + private IBinder mLeashedChild; + private IBinder mLeashedParent; + private long mLeashedParentNodeId = UNDEFINED_NODE_ID; + /** * Creates a new {@link AccessibilityNodeInfo}. */ @@ -1039,7 +1051,12 @@ public class AccessibilityNodeInfo implements Parcelable { return null; } final long childId = mChildNodeIds.get(index); - AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); + final AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); + if (mLeashedChild != null && childId == LEASHED_NODE_ID) { + return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mLeashedChild, + ROOT_NODE_ID, false, FLAG_PREFETCH_DESCENDANTS, null); + } + return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mWindowId, childId, false, FLAG_PREFETCH_DESCENDANTS, null); } @@ -1062,6 +1079,43 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * Adds a view root from leashed content as a child. This method is used to embedded another + * view hierarchy. + * <p> + * <strong>Note:</strong> Only one leashed child is permitted. + * </p> + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * Note that a view cannot be made its own child. + * </p> + * + * @param token The token to which a view root is added. + * + * @throws IllegalStateException If called from an AccessibilityService. + * @hide + */ + @TestApi + public void addChild(@NonNull IBinder token) { + enforceNotSealed(); + if (token == null) { + return; + } + if (mChildNodeIds == null) { + mChildNodeIds = new LongArray(); + } + + mLeashedChild = token; + // Checking uniqueness. + // Since only one leashed child is permitted, skip adding ID if the ID already exists. + if (mChildNodeIds.indexOf(LEASHED_NODE_ID) >= 0) { + return; + } + mChildNodeIds.add(LEASHED_NODE_ID); + } + + /** * Unchecked version of {@link #addChild(View)} that does not verify * uniqueness. For framework use only. * @@ -1090,6 +1144,38 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * Removes a leashed child. If the child was not previously added to the node, + * calling this method has no effect. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param token The token of the leashed child + * @return true if the child was present + * + * @throws IllegalStateException If called from an AccessibilityService. + * @hide + */ + public boolean removeChild(IBinder token) { + enforceNotSealed(); + if (mChildNodeIds == null || mLeashedChild == null) { + return false; + } + if (!mLeashedChild.equals(token)) { + return false; + } + final int index = mChildNodeIds.indexOf(LEASHED_NODE_ID); + mLeashedChild = null; + if (index < 0) { + return false; + } + mChildNodeIds.remove(index); + return true; + } + + /** * Adds a virtual child which is a descendant of the given <code>root</code>. * If <code>virtualDescendantId</code> is {@link View#NO_ID} the root * is added as a child. @@ -1668,6 +1754,9 @@ public class AccessibilityNodeInfo implements Parcelable { */ public AccessibilityNodeInfo getParent() { enforceSealed(); + if (mLeashedParent != null && mLeashedParentNodeId != UNDEFINED_NODE_ID) { + return getNodeForAccessibilityId(mConnectionId, mLeashedParent, mLeashedParentNodeId); + } return getNodeForAccessibilityId(mConnectionId, mWindowId, mParentNodeId); } @@ -3257,6 +3346,40 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * Sets the token and node id of the leashed parent. + * + * @param token The token. + * @param viewId The accessibility view id. + * @hide + */ + @TestApi + public void setLeashedParent(@Nullable IBinder token, int viewId) { + enforceNotSealed(); + mLeashedParent = token; + mLeashedParentNodeId = makeNodeId(viewId, AccessibilityNodeProvider.HOST_VIEW_ID); + } + + /** + * Gets the token of the leashed parent. + * + * @return The token. + * @hide + */ + public @Nullable IBinder getLeashedParent() { + return mLeashedParent; + } + + /** + * Gets the node id of the leashed parent. + * + * @return The accessibility node id. + * @hide + */ + public long getLeashedParentNodeId() { + return mLeashedParentNodeId; + } + + /** * Sets if this instance is sealed. * * @param sealed Whether is sealed. @@ -3559,6 +3682,18 @@ public class AccessibilityNodeInfo implements Parcelable { if (!Objects.equals(mTouchDelegateInfo, DEFAULT.mTouchDelegateInfo)) { nonDefaultFields |= bitAt(fieldIndex); } + fieldIndex++; + if (mLeashedChild != DEFAULT.mLeashedChild) { + nonDefaultFields |= bitAt(fieldIndex); + } + fieldIndex++; + if (mLeashedParent != DEFAULT.mLeashedParent) { + nonDefaultFields |= bitAt(fieldIndex); + } + fieldIndex++; + if (mLeashedParentNodeId != DEFAULT.mLeashedParentNodeId) { + nonDefaultFields |= bitAt(fieldIndex); + } int totalFields = fieldIndex; parcel.writeLong(nonDefaultFields); @@ -3685,6 +3820,16 @@ public class AccessibilityNodeInfo implements Parcelable { mTouchDelegateInfo.writeToParcel(parcel, flags); } + if (isBitSet(nonDefaultFields, fieldIndex++)) { + parcel.writeStrongBinder(mLeashedChild); + } + if (isBitSet(nonDefaultFields, fieldIndex++)) { + parcel.writeStrongBinder(mLeashedParent); + } + if (isBitSet(nonDefaultFields, fieldIndex++)) { + parcel.writeLong(mLeashedParentNodeId); + } + if (DEBUG) { fieldIndex--; if (totalFields != fieldIndex) { @@ -3768,6 +3913,10 @@ public class AccessibilityNodeInfo implements Parcelable { final TouchDelegateInfo otherInfo = other.mTouchDelegateInfo; mTouchDelegateInfo = (otherInfo != null) ? new TouchDelegateInfo(otherInfo.mTargetMap, true) : null; + + mLeashedChild = other.mLeashedChild; + mLeashedParent = other.mLeashedParent; + mLeashedParentNodeId = other.mLeashedParentNodeId; } private void initPoolingInfos(AccessibilityNodeInfo other) { @@ -3921,6 +4070,16 @@ public class AccessibilityNodeInfo implements Parcelable { mTouchDelegateInfo = TouchDelegateInfo.CREATOR.createFromParcel(parcel); } + if (isBitSet(nonDefaultFields, fieldIndex++)) { + mLeashedChild = parcel.readStrongBinder(); + } + if (isBitSet(nonDefaultFields, fieldIndex++)) { + mLeashedParent = parcel.readStrongBinder(); + } + if (isBitSet(nonDefaultFields, fieldIndex++)) { + mLeashedParentNodeId = parcel.readLong(); + } + mSealed = sealed; } @@ -4200,6 +4359,19 @@ public class AccessibilityNodeInfo implements Parcelable { | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS, null); } + private static AccessibilityNodeInfo getNodeForAccessibilityId(int connectionId, + IBinder leashToken, long accessibilityId) { + if (!((leashToken != null) + && (getAccessibilityViewId(accessibilityId) != UNDEFINED_ITEM_ID) + && (connectionId != UNDEFINED_CONNECTION_ID))) { + return null; + } + AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); + return client.findAccessibilityNodeInfoByAccessibilityId(connectionId, + leashToken, accessibilityId, false, FLAG_PREFETCH_PREDECESSORS + | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS, null); + } + /** @hide */ public static String idToString(long accessibilityId) { int accessibilityViewId = getAccessibilityViewId(accessibilityId); diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl index 392db574d988..fcaaa2e74778 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -66,12 +66,12 @@ interface IAccessibilityManager { // Used by UiAutomation IBinder getWindowToken(int windowId, int userId); - void notifyAccessibilityButtonClicked(int displayId); + void notifyAccessibilityButtonClicked(int displayId, String targetName); void notifyAccessibilityButtonVisibilityChanged(boolean available); // Requires Manifest.permission.MANAGE_ACCESSIBILITY - void performAccessibilityShortcut(); + void performAccessibilityShortcut(String targetName); // Requires Manifest.permission.MANAGE_ACCESSIBILITY List<String> getAccessibilityShortcutTargets(int shortcutType); diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 9c04b392b9d3..c159f89cf2ac 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -230,6 +230,7 @@ public final class AutofillManager { /** @hide */ public static final int ACTION_VIEW_ENTERED = 2; /** @hide */ public static final int ACTION_VIEW_EXITED = 3; /** @hide */ public static final int ACTION_VALUE_CHANGED = 4; + /** @hide */ public static final int ACTION_RESPONSE_EXPIRED = 5; /** @hide */ public static final int NO_LOGGING = 0; /** @hide */ public static final int FLAG_ADD_CLIENT_ENABLED = 0x1; @@ -776,11 +777,19 @@ public final class AutofillManager { * * @see AutofillClient#autofillClientIsVisibleForAutofill() * + * @param isExpiredResponse The response has expired or not + * * {@hide} */ - public void onInvisibleForAutofill() { + public void onInvisibleForAutofill(boolean isExpiredResponse) { synchronized (mLock) { mOnInvisibleCalled = true; + + if (isExpiredResponse) { + // Notify service the response has expired. + updateSessionLocked(/* id= */ null, /* bounds= */ null, /* value= */ null, + ACTION_RESPONSE_EXPIRED, /* flags= */ 0); + } } } diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java index ae2fb8ebc24a..d5d631ac1dc7 100644 --- a/core/java/android/view/inputmethod/BaseInputConnection.java +++ b/core/java/android/view/inputmethod/BaseInputConnection.java @@ -758,8 +758,9 @@ public class BaseInputConnection implements InputConnection { Context context; if (mTargetView != null) { context = mTargetView.getContext(); - } else if (mIMM.mServedView != null) { - context = mIMM.mServedView.getContext(); + } else if (mIMM.mCurRootView != null) { + final View servedView = mIMM.mCurRootView.getImeFocusController().getServedView(); + context = servedView != null ? servedView.getContext() : null; } else { context = null; } diff --git a/core/java/android/view/inputmethod/InlineSuggestion.java b/core/java/android/view/inputmethod/InlineSuggestion.java index c10144e6ee25..a32ea4b66481 100644 --- a/core/java/android/view/inputmethod/InlineSuggestion.java +++ b/core/java/android/view/inputmethod/InlineSuggestion.java @@ -19,6 +19,7 @@ package android.view.inputmethod; import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.content.Context; import android.os.AsyncTask; import android.os.Parcelable; @@ -61,6 +62,20 @@ public final class InlineSuggestion implements Parcelable { private final @Nullable IInlineContentProvider mContentProvider; /** + * Creates a new {@link InlineSuggestion}, for testing purpose. + * + * @hide + */ + @TestApi + @NonNull + public static InlineSuggestion newInlineSuggestion(@NonNull InlineSuggestionInfo info) { + return new InlineSuggestion(info, null); + } + + + + + /** * Inflates a view with the content of this suggestion at a specific size. * The size must be between the {@link InlinePresentationSpec#getMinSize() min size} * and the {@link InlinePresentationSpec#getMaxSize() max size} of the presentation @@ -271,10 +286,10 @@ public final class InlineSuggestion implements Parcelable { }; @DataClass.Generated( - time = 1575933636929L, + time = 1578972138081L, codegenVersion = "1.0.14", sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestion.java", - inputSignatures = "private static final java.lang.String TAG\nprivate final @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo mInfo\nprivate final @android.annotation.Nullable com.android.internal.view.inline.IInlineContentProvider mContentProvider\npublic void inflate(android.content.Context,android.util.Size,java.util.concurrent.Executor,java.util.function.Consumer<android.view.View>)\nclass InlineSuggestion extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)") + inputSignatures = "private static final java.lang.String TAG\nprivate final @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo mInfo\nprivate final @android.annotation.Nullable com.android.internal.view.inline.IInlineContentProvider mContentProvider\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestion newInlineSuggestion(android.view.inputmethod.InlineSuggestionInfo)\npublic void inflate(android.content.Context,android.util.Size,java.util.concurrent.Executor,java.util.function.Consumer<android.view.View>)\nclass InlineSuggestion extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/view/inputmethod/InlineSuggestionInfo.java b/core/java/android/view/inputmethod/InlineSuggestionInfo.java index 07fce3101d85..195b63a8fc8e 100644 --- a/core/java/android/view/inputmethod/InlineSuggestionInfo.java +++ b/core/java/android/view/inputmethod/InlineSuggestionInfo.java @@ -18,6 +18,7 @@ package android.view.inputmethod; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.os.Parcelable; import android.view.inline.InlinePresentationSpec; @@ -53,6 +54,19 @@ public final class InlineSuggestionInfo implements Parcelable { /** Hints for the type of data being suggested. */ private final @Nullable String[] mAutofillHints; + /** + * Creates a new {@link InlineSuggestionInfo}, for testing purpose. + * + * @hide + */ + @TestApi + @NonNull + public static InlineSuggestionInfo newInlineSuggestionInfo( + @NonNull InlinePresentationSpec presentationSpec, + @NonNull @Source String source, + @Nullable String[] autofillHints) { + return new InlineSuggestionInfo(presentationSpec, source, autofillHints); + } @@ -247,10 +261,10 @@ public final class InlineSuggestionInfo implements Parcelable { }; @DataClass.Generated( - time = 1574406074120L, + time = 1578972121865L, codegenVersion = "1.0.14", sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionInfo.java", - inputSignatures = "public static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_AUTOFILL\npublic static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_PLATFORM\nprivate final @android.annotation.NonNull android.view.inline.InlinePresentationSpec mPresentationSpec\nprivate final @android.annotation.NonNull @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String mSource\nprivate final @android.annotation.Nullable java.lang.String[] mAutofillHints\nclass InlineSuggestionInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)") + inputSignatures = "public static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_AUTOFILL\npublic static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_PLATFORM\nprivate final @android.annotation.NonNull android.view.inline.InlinePresentationSpec mPresentationSpec\nprivate final @android.annotation.NonNull @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String mSource\nprivate final @android.annotation.Nullable java.lang.String[] mAutofillHints\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo newInlineSuggestionInfo(android.view.inline.InlinePresentationSpec,java.lang.String,java.lang.String[])\nclass InlineSuggestionInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java index 386c9cb9d14f..860ce90d5fc0 100644 --- a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java +++ b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java @@ -17,6 +17,8 @@ package android.view.inputmethod; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityThread; import android.os.Parcelable; import android.view.inline.InlinePresentationSpec; @@ -49,6 +51,21 @@ public final class InlineSuggestionsRequest implements Parcelable { */ private final @NonNull List<InlinePresentationSpec> mPresentationSpecs; + /** + * The package name of the app that requests for the inline suggestions and will host the + * embedded suggestion views. The app does not have to set the value for the field because + * it'll be set by the system for safety reasons. + */ + private @NonNull String mHostPackageName; + + /** + * @hide + * @see {@link #mHostPackageName}. + */ + public void setHostPackageName(@NonNull String hostPackageName) { + mHostPackageName = hostPackageName; + } + private void onConstructed() { Preconditions.checkState(mMaxSuggestionCount >= mPresentationSpecs.size()); } @@ -57,9 +74,15 @@ public final class InlineSuggestionsRequest implements Parcelable { return SUGGESTION_COUNT_UNLIMITED; } + private static String defaultHostPackageName() { + return ActivityThread.currentPackageName(); + } + /** @hide */ abstract static class BaseBuilder { abstract Builder setPresentationSpecs(@NonNull List<InlinePresentationSpec> value); + + abstract Builder setHostPackageName(@Nullable String value); } @@ -80,11 +103,15 @@ public final class InlineSuggestionsRequest implements Parcelable { @DataClass.Generated.Member /* package-private */ InlineSuggestionsRequest( int maxSuggestionCount, - @NonNull List<InlinePresentationSpec> presentationSpecs) { + @NonNull List<InlinePresentationSpec> presentationSpecs, + @NonNull String hostPackageName) { this.mMaxSuggestionCount = maxSuggestionCount; this.mPresentationSpecs = presentationSpecs; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mPresentationSpecs); + this.mHostPackageName = hostPackageName; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mHostPackageName); onConstructed(); } @@ -108,6 +135,16 @@ public final class InlineSuggestionsRequest implements Parcelable { return mPresentationSpecs; } + /** + * The package name of the app that requests for the inline suggestions and will host the + * embedded suggestion views. The app does not have to set the value for the field because + * it'll be set by the system for safety reasons. + */ + @DataClass.Generated.Member + public @NonNull String getHostPackageName() { + return mHostPackageName; + } + @Override @DataClass.Generated.Member public String toString() { @@ -116,13 +153,14 @@ public final class InlineSuggestionsRequest implements Parcelable { return "InlineSuggestionsRequest { " + "maxSuggestionCount = " + mMaxSuggestionCount + ", " + - "presentationSpecs = " + mPresentationSpecs + + "presentationSpecs = " + mPresentationSpecs + ", " + + "hostPackageName = " + mHostPackageName + " }"; } @Override @DataClass.Generated.Member - public boolean equals(@android.annotation.Nullable Object o) { + public boolean equals(@Nullable Object o) { // You can override field equality logic by defining either of the methods like: // boolean fieldNameEquals(InlineSuggestionsRequest other) { ... } // boolean fieldNameEquals(FieldType otherValue) { ... } @@ -134,7 +172,8 @@ public final class InlineSuggestionsRequest implements Parcelable { //noinspection PointlessBooleanExpression return true && mMaxSuggestionCount == that.mMaxSuggestionCount - && java.util.Objects.equals(mPresentationSpecs, that.mPresentationSpecs); + && java.util.Objects.equals(mPresentationSpecs, that.mPresentationSpecs) + && java.util.Objects.equals(mHostPackageName, that.mHostPackageName); } @Override @@ -146,6 +185,7 @@ public final class InlineSuggestionsRequest implements Parcelable { int _hash = 1; _hash = 31 * _hash + mMaxSuggestionCount; _hash = 31 * _hash + java.util.Objects.hashCode(mPresentationSpecs); + _hash = 31 * _hash + java.util.Objects.hashCode(mHostPackageName); return _hash; } @@ -157,6 +197,7 @@ public final class InlineSuggestionsRequest implements Parcelable { dest.writeInt(mMaxSuggestionCount); dest.writeParcelableList(mPresentationSpecs, flags); + dest.writeString(mHostPackageName); } @Override @@ -173,11 +214,15 @@ public final class InlineSuggestionsRequest implements Parcelable { int maxSuggestionCount = in.readInt(); List<InlinePresentationSpec> presentationSpecs = new ArrayList<>(); in.readParcelableList(presentationSpecs, InlinePresentationSpec.class.getClassLoader()); + String hostPackageName = in.readString(); this.mMaxSuggestionCount = maxSuggestionCount; this.mPresentationSpecs = presentationSpecs; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mPresentationSpecs); + this.mHostPackageName = hostPackageName; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mHostPackageName); onConstructed(); } @@ -205,6 +250,7 @@ public final class InlineSuggestionsRequest implements Parcelable { private int mMaxSuggestionCount; private @NonNull List<InlinePresentationSpec> mPresentationSpecs; + private @NonNull String mHostPackageName; private long mBuilderFieldsSet = 0L; @@ -260,22 +306,40 @@ public final class InlineSuggestionsRequest implements Parcelable { return this; } + /** + * The package name of the app that requests for the inline suggestions and will host the + * embedded suggestion views. The app does not have to set the value for the field because + * it'll be set by the system for safety reasons. + */ + @DataClass.Generated.Member + @Override + @NonNull Builder setHostPackageName(@NonNull String value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; + mHostPackageName = value; + return this; + } + /** Builds the instance. This builder should not be touched after calling this! */ public @NonNull InlineSuggestionsRequest build() { checkNotUsed(); - mBuilderFieldsSet |= 0x4; // Mark builder used + mBuilderFieldsSet |= 0x8; // Mark builder used if ((mBuilderFieldsSet & 0x1) == 0) { mMaxSuggestionCount = defaultMaxSuggestionCount(); } + if ((mBuilderFieldsSet & 0x4) == 0) { + mHostPackageName = defaultHostPackageName(); + } InlineSuggestionsRequest o = new InlineSuggestionsRequest( mMaxSuggestionCount, - mPresentationSpecs); + mPresentationSpecs, + mHostPackageName); return o; } private void checkNotUsed() { - if ((mBuilderFieldsSet & 0x4) != 0) { + if ((mBuilderFieldsSet & 0x8) != 0) { throw new IllegalStateException( "This Builder should not be reused. Use a new Builder instance instead"); } @@ -283,10 +347,10 @@ public final class InlineSuggestionsRequest implements Parcelable { } @DataClass.Generated( - time = 1576637222199L, + time = 1578948035951L, codegenVersion = "1.0.14", sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java", - inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.view.inline.InlinePresentationSpec> mPresentationSpecs\nprivate void onConstructed()\nprivate static int defaultMaxSuggestionCount()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setPresentationSpecs(java.util.List<android.view.inline.InlinePresentationSpec>)\nclass BaseBuilder extends java.lang.Object implements []") + inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.view.inline.InlinePresentationSpec> mPresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\npublic void setHostPackageName(java.lang.String)\nprivate void onConstructed()\nprivate static int defaultMaxSuggestionCount()\nprivate static java.lang.String defaultHostPackageName()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setPresentationSpecs(java.util.List<android.view.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nclass BaseBuilder extends java.lang.Object implements []") @Deprecated private void __metadata() {} diff --git a/core/java/android/view/inputmethod/InlineSuggestionsResponse.java b/core/java/android/view/inputmethod/InlineSuggestionsResponse.java index 924a5eee784b..be833df61ec4 100644 --- a/core/java/android/view/inputmethod/InlineSuggestionsResponse.java +++ b/core/java/android/view/inputmethod/InlineSuggestionsResponse.java @@ -18,6 +18,7 @@ package android.view.inputmethod; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.os.Parcelable; import com.android.internal.util.DataClass; @@ -33,6 +34,18 @@ import java.util.List; public final class InlineSuggestionsResponse implements Parcelable { private final @NonNull List<InlineSuggestion> mInlineSuggestions; + /** + * Creates a new {@link InlineSuggestionsResponse}, for testing purpose. + * + * @hide + */ + @TestApi + @NonNull + public static InlineSuggestionsResponse newInlineSuggestionsResponse( + @NonNull List<InlineSuggestion> inlineSuggestions) { + return new InlineSuggestionsResponse(inlineSuggestions); + } + // Code below generated by codegen v1.0.14. @@ -151,10 +164,10 @@ public final class InlineSuggestionsResponse implements Parcelable { }; @DataClass.Generated( - time = 1574406147911L, + time = 1578972149519L, codegenVersion = "1.0.14", sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsResponse.java", - inputSignatures = "private final @android.annotation.NonNull java.util.List<android.view.inputmethod.InlineSuggestion> mInlineSuggestions\nclass InlineSuggestionsResponse extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstructor=true)") + inputSignatures = "private final @android.annotation.NonNull java.util.List<android.view.inputmethod.InlineSuggestion> mInlineSuggestions\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestionsResponse newInlineSuggestionsResponse(java.util.List<android.view.inputmethod.InlineSuggestion>)\nclass InlineSuggestionsResponse extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstructor=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index f3007a794344..904e736d214e 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -64,6 +64,7 @@ import android.view.InputChannel; import android.view.InputEvent; import android.view.InputEventSender; import android.view.KeyEvent; +import android.view.ImeFocusController; import android.view.View; import android.view.ViewRootImpl; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; @@ -364,10 +365,10 @@ public final class InputMethodManager { boolean mActive = false; /** - * {@code true} if next {@link #onPostWindowFocus(View, View, int, int)} needs to + * {@code true} if next {@link ImeFocusController#onPostWindowFocus} needs to * restart input. */ - boolean mRestartOnNextWindowFocus = true; + private boolean mRestartOnNextWindowFocus = true; /** * As reported by IME through InputConnection. @@ -380,22 +381,8 @@ public final class InputMethodManager { * This is the root view of the overall window that currently has input * method focus. */ - @UnsupportedAppUsage - View mCurRootView; - /** - * This is the view that should currently be served by an input method, - * regardless of the state of setting that up. - */ - // See comment to mH field in regard to @UnsupportedAppUsage - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) - View mServedView; - /** - * This is then next view that will be served by the input method, when - * we get around to updating things. - */ - // See comment to mH field in regard to @UnsupportedAppUsage - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) - View mNextServedView; + @GuardedBy("mH") + ViewRootImpl mCurRootView; /** * This is set when we are in the process of connecting, to determine * when we have actually finished. @@ -489,6 +476,8 @@ public final class InputMethodManager { final Pool<PendingEvent> mPendingEventPool = new SimplePool<>(20); final SparseArray<PendingEvent> mPendingEvents = new SparseArray<>(20); + final DelegateImpl mDelegate = new DelegateImpl(); + // ----------------------------------------------------------- static final int MSG_DUMP = 1; @@ -564,6 +553,178 @@ public final class InputMethodManager { return servedView.hasWindowFocus() || isAutofillUIShowing(servedView); } + private final class DelegateImpl implements + ImeFocusController.InputMethodManagerDelegate { + /** + * Used by {@link ImeFocusController} to start input connection. + */ + @Override + public boolean startInput(@StartInputReason int startInputReason, View focusedView, + @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode, + int windowFlags) { + synchronized (mH) { + mCurrentTextBoxAttribute = null; + mCompletions = null; + mServedConnecting = true; + if (getServedViewLocked() != null && !getServedViewLocked().onCheckIsTextEditor()) { + // servedView has changed and it's not editable. + maybeCallServedViewChangedLocked(null); + } + } + return startInputInner(startInputReason, + focusedView != null ? focusedView.getWindowToken() : null, startInputFlags, + softInputMode, windowFlags); + } + + /** + * Used by {@link ImeFocusController} to finish input connection. + */ + @Override + public void finishInput() { + synchronized (mH) { + finishInputLocked(); + } + } + + /** + * Used by {@link ImeFocusController} to hide current input method editor. + */ + @Override + public void closeCurrentIme() { + closeCurrentInput(); + } + + /** + * For {@link ImeFocusController} to start input asynchronously when focus gain. + */ + @Override + public void startInputAsyncOnWindowFocusGain(View focusedView, + @SoftInputModeFlags int softInputMode, int windowFlags, boolean forceNewFocus) { + final boolean forceNewFocus1 = forceNewFocus; + final int startInputFlags = getStartInputFlags(focusedView, 0); + + if (mWindowFocusGainFuture != null) { + mWindowFocusGainFuture.cancel(false /* mayInterruptIfRunning */); + } + mWindowFocusGainFuture = mStartInputWorker.submit(() -> { + synchronized (mH) { + if (mCurRootView == null) { + return; + } + if (mCurRootView.getImeFocusController().checkFocus(forceNewFocus1, false)) { + // We need to restart input on the current focus view. This + // should be done in conjunction with telling the system service + // about the window gaining focus, to help make the transition + // smooth. + if (startInput(StartInputReason.WINDOW_FOCUS_GAIN, + focusedView, startInputFlags, softInputMode, windowFlags)) { + return; + } + } + + // For some reason we didn't do a startInput + windowFocusGain, so + // we'll just do a window focus gain and call it a day. + try { + if (DEBUG) Log.v(TAG, "Reporting focus gain, without startInput"); + mService.startInputOrWindowGainedFocus( + StartInputReason.WINDOW_FOCUS_GAIN_REPORT_ONLY, mClient, + focusedView.getWindowToken(), startInputFlags, softInputMode, + windowFlags, + null, null, 0 /* missingMethodFlags */, + mCurRootView.mContext.getApplicationInfo().targetSdkVersion); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + }); + } + + /** + * Used by {@link ImeFocusController} to finish current composing text. + */ + @Override + public void finishComposingText() { + if (mServedInputConnectionWrapper != null) { + mServedInputConnectionWrapper.finishComposingText(); + } + } + + /** + * Used for {@link ImeFocusController} to set the current focused root view. + */ + @Override + public void setCurrentRootView(ViewRootImpl rootView) { + // If the mCurRootView is losing window focus, release the strong reference to it + // so as not to prevent it from being garbage-collected. + if (mWindowFocusGainFuture != null) { + mWindowFocusGainFuture.cancel(false /* mayInterruptIfRunning */); + mWindowFocusGainFuture = null; + } + synchronized (mH) { + mCurRootView = rootView; + } + } + + /** + * Used for {@link ImeFocusController} to return if the root view from the + * controller is this {@link InputMethodManager} currently focused. + * TODO: Address event-order problem when get current root view in multi-threads. + */ + @Override + public boolean isCurrentRootView(ViewRootImpl rootView) { + synchronized (mH) { + return mCurRootView == rootView; + } + } + + /** + * For {@link ImeFocusController#checkFocus} if needed to force check new focus. + */ + @Override + public boolean isRestartOnNextWindowFocus(boolean reset) { + final boolean result = mRestartOnNextWindowFocus; + if (reset) { + mRestartOnNextWindowFocus = false; + } + return result; + } + } + + /** @hide */ + public DelegateImpl getDelegate() { + return mDelegate; + } + + private View getServedViewLocked() { + return mCurRootView != null ? mCurRootView.getImeFocusController().getServedView() : null; + } + + private View getNextServedViewLocked() { + return mCurRootView != null ? mCurRootView.getImeFocusController().getNextServedView() + : null; + } + + private void setServedViewLocked(View view) { + if (mCurRootView != null) { + mCurRootView.getImeFocusController().setServedView(view); + } + } + + private void setNextServedViewLocked(View view) { + if (mCurRootView != null) { + mCurRootView.getImeFocusController().setNextServedView(view); + } + } + + /** + * Returns {@code true} when the given view has been served by Input Method. + */ + private boolean hasServedByInputMethodLocked(View view) { + final View servedView = getServedViewLocked(); + return (servedView == view + || (servedView != null && servedView.checkInputConnectionProxy(view))); + } + class H extends Handler { H(Looper looper) { super(looper, null, true); @@ -629,7 +790,8 @@ public final class InputMethodManager { clearBindingLocked(); // If we were actively using the last input method, then // we would like to re-connect to the next input method. - if (mServedView != null && mServedView.isFocused()) { + final View servedView = getServedViewLocked(); + if (servedView != null && servedView.isFocused()) { mServedConnecting = true; } startInput = mActive; @@ -664,11 +826,16 @@ public final class InputMethodManager { } // Check focus again in case that "onWindowFocus" is called before // handling this message. - if (mServedView != null && canStartInput(mServedView)) { - if (checkFocusNoStartInput(mRestartOnNextWindowFocus)) { + final View servedView; + synchronized (mH) { + servedView = getServedViewLocked(); + } + if (servedView != null && canStartInput(servedView)) { + if (mCurRootView != null && mCurRootView.getImeFocusController() + .checkFocus(mRestartOnNextWindowFocus, false)) { final int reason = active ? StartInputReason.ACTIVATED_BY_IMMS : StartInputReason.DEACTIVATED_BY_IMMS; - startInputInner(reason, null, 0, 0, 0); + mDelegate.startInput(reason, null, 0, 0, 0); } } return; @@ -1212,10 +1379,7 @@ public final class InputMethodManager { checkFocus(); synchronized (mH) { - return (mServedView == view - || (mServedView != null - && mServedView.checkInputConnectionProxy(view))) - && mCurrentTextBoxAttribute != null; + return hasServedByInputMethodLocked(view) && mCurrentTextBoxAttribute != null; } } @@ -1225,7 +1389,7 @@ public final class InputMethodManager { public boolean isActive() { checkFocus(); synchronized (mH) { - return mServedView != null && mCurrentTextBoxAttribute != null; + return getServedViewLocked() != null && mCurrentTextBoxAttribute != null; } } @@ -1286,11 +1450,14 @@ public final class InputMethodManager { */ @UnsupportedAppUsage void finishInputLocked() { - mNextServedView = null; mActivityViewToScreenMatrix = null; - if (mServedView != null) { - if (DEBUG) Log.v(TAG, "FINISH INPUT: mServedView=" + dumpViewInfo(mServedView)); - mServedView = null; + setNextServedViewLocked(null); + if (getServedViewLocked() != null) { + if (DEBUG) { + Log.v(TAG, "FINISH INPUT: mServedView=" + + dumpViewInfo(getServedViewLocked())); + } + setServedViewLocked(null); mCompletions = null; mServedConnecting = false; clearConnectionLocked(); @@ -1307,8 +1474,7 @@ public final class InputMethodManager { checkFocus(); synchronized (mH) { - if (mServedView != view && (mServedView == null - || !mServedView.checkInputConnectionProxy(view))) { + if (!hasServedByInputMethodLocked(view)) { return; } @@ -1332,8 +1498,7 @@ public final class InputMethodManager { checkFocus(); synchronized (mH) { - if (mServedView != view && (mServedView == null - || !mServedView.checkInputConnectionProxy(view))) { + if (!hasServedByInputMethodLocked(view)) { return; } @@ -1447,8 +1612,7 @@ public final class InputMethodManager { checkFocus(); synchronized (mH) { - if (mServedView != view && (mServedView == null - || !mServedView.checkInputConnectionProxy(view))) { + if (!hasServedByInputMethodLocked(view)) { return false; } @@ -1539,7 +1703,8 @@ public final class InputMethodManager { ResultReceiver resultReceiver) { checkFocus(); synchronized (mH) { - if (mServedView == null || mServedView.getWindowToken() != windowToken) { + final View servedView = getServedViewLocked(); + if (servedView == null || servedView.getWindowToken() != windowToken) { return false; } @@ -1566,7 +1731,8 @@ public final class InputMethodManager { **/ public void toggleSoftInputFromWindow(IBinder windowToken, int showFlags, int hideFlags) { synchronized (mH) { - if (mServedView == null || mServedView.getWindowToken() != windowToken) { + final View servedView = getServedViewLocked(); + if (servedView == null || servedView.getWindowToken() != windowToken) { return; } if (mCurMethod != null) { @@ -1617,8 +1783,7 @@ public final class InputMethodManager { checkFocus(); synchronized (mH) { - if (mServedView != view && (mServedView == null - || !mServedView.checkInputConnectionProxy(view))) { + if (!hasServedByInputMethodLocked(view)) { return; } @@ -1645,7 +1810,7 @@ public final class InputMethodManager { final View view; synchronized (mH) { - view = mServedView; + view = getServedViewLocked(); // Make sure we have a window token for the served view. if (DEBUG) { @@ -1664,10 +1829,7 @@ public final class InputMethodManager { Log.e(TAG, "ABORT input: ServedView must be attached to a Window"); return false; } - startInputFlags |= StartInputFlags.VIEW_HAS_FOCUS; - if (view.onCheckIsTextEditor()) { - startInputFlags |= StartInputFlags.IS_TEXT_EDITOR; - } + startInputFlags = getStartInputFlags(view, startInputFlags); softInputMode = view.getViewRootImpl().mWindowAttributes.softInputMode; windowFlags = view.getViewRootImpl().mWindowAttributes.flags; } @@ -1690,7 +1852,7 @@ public final class InputMethodManager { // The view is running on a different thread than our own, so // we need to reschedule our work for over there. if (DEBUG) Log.v(TAG, "Starting input: reschedule to view thread"); - vh.post(() -> startInputInner(startInputReason, null, 0, 0, 0)); + vh.post(() -> mDelegate.startInput(startInputReason, null, 0, 0, 0)); return false; } @@ -1709,11 +1871,12 @@ public final class InputMethodManager { synchronized (mH) { // Now that we are locked again, validate that our state hasn't // changed. - if (mServedView != view || !mServedConnecting) { + final View servedView = getServedViewLocked(); + if (servedView != view || !mServedConnecting) { // Something else happened, so abort. if (DEBUG) Log.v(TAG, "Starting input: finished by someone else. view=" + dumpViewInfo(view) - + " mServedView=" + dumpViewInfo(mServedView) + + " servedView=" + dumpViewInfo(servedView) + " mServedConnecting=" + mServedConnecting); return false; } @@ -1804,101 +1967,12 @@ public final class InputMethodManager { return true; } - /** - * When the focused window is dismissed, this method is called to finish the - * input method started before. - * @hide - */ - @UnsupportedAppUsage - public void windowDismissed(IBinder appWindowToken) { - checkFocus(); - synchronized (mH) { - if (mServedView != null && - mServedView.getWindowToken() == appWindowToken) { - finishInputLocked(); - } - if (mCurRootView != null && - mCurRootView.getWindowToken() == appWindowToken) { - mCurRootView = null; - } - } - } - - /** - * Call this when a view receives focus. - * @hide - */ - @UnsupportedAppUsage - public void focusIn(View view) { - synchronized (mH) { - focusInLocked(view); - } - } - - void focusInLocked(View view) { - if (DEBUG) Log.v(TAG, "focusIn: " + dumpViewInfo(view)); - - if (view != null && view.isTemporarilyDetached()) { - // This is a request from a view that is temporarily detached from a window. - if (DEBUG) Log.v(TAG, "Temporarily detached view, ignoring"); - return; - } - - if (mCurRootView != view.getRootView()) { - // This is a request from a window that isn't in the window with - // IME focus, so ignore it. - if (DEBUG) Log.v(TAG, "Not IME target window, ignoring"); - return; - } - - mNextServedView = view; - scheduleCheckFocusLocked(view); - } - - /** - * Call this when a view loses focus. - * @hide - */ - @UnsupportedAppUsage - public void focusOut(View view) { - synchronized (mH) { - if (DEBUG) Log.v(TAG, "focusOut: view=" + dumpViewInfo(view) - + " mServedView=" + dumpViewInfo(mServedView)); - if (mServedView != view) { - // The following code would auto-hide the IME if we end up - // with no more views with focus. This can happen, however, - // whenever we go into touch mode, so it ends up hiding - // at times when we don't really want it to. For now it - // seems better to just turn it all off. - // TODO: Check view.isTemporarilyDetached() when re-enable the following code. - if (false && canStartInput(view)) { - mNextServedView = null; - scheduleCheckFocusLocked(view); - } - } - } - } - - /** - * Call this when a view is being detached from a {@link android.view.Window}. - * @hide - */ - public void onViewDetachedFromWindow(View view) { - synchronized (mH) { - if (DEBUG) Log.v(TAG, "onViewDetachedFromWindow: view=" + dumpViewInfo(view) - + " mServedView=" + dumpViewInfo(mServedView)); - if (mServedView == view) { - mNextServedView = null; - scheduleCheckFocusLocked(view); - } - } - } - - static void scheduleCheckFocusLocked(View view) { - ViewRootImpl viewRootImpl = view.getViewRootImpl(); - if (viewRootImpl != null) { - viewRootImpl.dispatchCheckFocus(); + private int getStartInputFlags(View focusedView, int startInputFlags) { + startInputFlags |= StartInputFlags.VIEW_HAS_FOCUS; + if (focusedView.onCheckIsTextEditor()) { + startInputFlags |= StartInputFlags.IS_TEXT_EDITOR; } + return startInputFlags; } /** @@ -1906,54 +1980,12 @@ public final class InputMethodManager { */ @UnsupportedAppUsage public void checkFocus() { - if (checkFocusNoStartInput(false)) { - startInputInner(StartInputReason.CHECK_FOCUS, null, 0, 0, 0); - } - } - - private boolean checkFocusNoStartInput(boolean forceNewFocus) { - // This is called a lot, so short-circuit before locking. - if (mServedView == mNextServedView && !forceNewFocus) { - return false; - } - - final ControlledInputConnectionWrapper ic; synchronized (mH) { - if (mServedView == mNextServedView && !forceNewFocus) { - return false; - } - if (DEBUG) Log.v(TAG, "checkFocus: view=" + mServedView - + " next=" + mNextServedView - + " forceNewFocus=" + forceNewFocus - + " package=" - + (mServedView != null ? mServedView.getContext().getPackageName() : "<none>")); - - if (mNextServedView == null) { - finishInputLocked(); - // In this case, we used to have a focused view on the window, - // but no longer do. We should make sure the input method is - // no longer shown, since it serves no purpose. - closeCurrentInput(); - return false; - } - - ic = mServedInputConnectionWrapper; - - mServedView = mNextServedView; - mCurrentTextBoxAttribute = null; - mCompletions = null; - mServedConnecting = true; - // servedView has changed and it's not editable. - if (!mServedView.onCheckIsTextEditor()) { - maybeCallServedViewChangedLocked(null); + if (mCurRootView != null) { + mCurRootView.getImeFocusController().checkFocus(false /* forceNewFocus */, + true /* startInput */); } } - - if (ic != null) { - ic.finishComposingText(); - } - - return true; } @UnsupportedAppUsage @@ -1966,92 +1998,6 @@ public final class InputMethodManager { } /** - * Called by ViewAncestor when its window gets input focus. - * @hide - */ - public void onPostWindowFocus(View rootView, View focusedView, - @SoftInputModeFlags int softInputMode, int windowFlags) { - boolean forceNewFocus = false; - synchronized (mH) { - if (DEBUG) Log.v(TAG, "onWindowFocus: " + focusedView - + " softInputMode=" + InputMethodDebug.softInputModeToString(softInputMode) - + " flags=#" + Integer.toHexString(windowFlags)); - if (mRestartOnNextWindowFocus) { - if (DEBUG) Log.v(TAG, "Restarting due to mRestartOnNextWindowFocus"); - mRestartOnNextWindowFocus = false; - forceNewFocus = true; - } - focusInLocked(focusedView != null ? focusedView : rootView); - } - - int startInputFlags = 0; - if (focusedView != null) { - startInputFlags |= StartInputFlags.VIEW_HAS_FOCUS; - if (focusedView.onCheckIsTextEditor()) { - startInputFlags |= StartInputFlags.IS_TEXT_EDITOR; - } - } - - final boolean forceNewFocus1 = forceNewFocus; - final int startInputFlags1 = startInputFlags; - if (mWindowFocusGainFuture != null) { - mWindowFocusGainFuture.cancel(false/* mayInterruptIfRunning */); - } - mWindowFocusGainFuture = mStartInputWorker.submit(() -> { - if (checkFocusNoStartInput(forceNewFocus1)) { - // We need to restart input on the current focus view. This - // should be done in conjunction with telling the system service - // about the window gaining focus, to help make the transition - // smooth. - if (startInputInner(StartInputReason.WINDOW_FOCUS_GAIN, rootView.getWindowToken(), - startInputFlags1, softInputMode, windowFlags)) { - return; - } - } - - // For some reason we didn't do a startInput + windowFocusGain, so - // we'll just do a window focus gain and call it a day. - synchronized (mH) { - try { - if (DEBUG) Log.v(TAG, "Reporting focus gain, without startInput"); - mService.startInputOrWindowGainedFocus( - StartInputReason.WINDOW_FOCUS_GAIN_REPORT_ONLY, mClient, - rootView.getWindowToken(), startInputFlags1, softInputMode, windowFlags, - null, null, 0 /* missingMethodFlags */, - rootView.getContext().getApplicationInfo().targetSdkVersion); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - }); - } - - /** @hide */ - @UnsupportedAppUsage - public void onPreWindowFocus(View rootView, boolean hasWindowFocus) { - synchronized (mH) { - if (rootView == null) { - mCurRootView = null; - } if (hasWindowFocus) { - mCurRootView = rootView; - } else if (rootView == mCurRootView) { - // If the mCurRootView is losing window focus, release the strong reference to it - // so as not to prevent it from being garbage-collected. - mCurRootView = null; - if (mWindowFocusGainFuture != null) { - mWindowFocusGainFuture.cancel(false /* mayInterruptIfRunning */); - mWindowFocusGainFuture = null; - } - } else { - if (DEBUG) { - Log.v(TAG, "Ignoring onPreWindowFocus()." - + " mCurRootView=" + mCurRootView + " rootView=" + rootView); - } - } - } - } - - /** * Register for IME state callbacks and applying visibility in * {@link android.view.ImeInsetsSourceConsumer}. * @hide @@ -2090,10 +2036,11 @@ public final class InputMethodManager { */ public boolean requestImeShow(ResultReceiver resultReceiver) { synchronized (mH) { - if (mServedView == null) { + final View servedView = getServedViewLocked(); + if (servedView == null) { return false; } - showSoftInput(mServedView, 0 /* flags */, resultReceiver); + showSoftInput(servedView, 0 /* flags */, resultReceiver); return true; } } @@ -2135,9 +2082,8 @@ public final class InputMethodManager { checkFocus(); synchronized (mH) { - if ((mServedView != view && (mServedView == null - || !mServedView.checkInputConnectionProxy(view))) - || mCurrentTextBoxAttribute == null || mCurMethod == null) { + if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null + || mCurMethod == null) { return; } @@ -2185,12 +2131,17 @@ public final class InputMethodManager { return; } - final boolean focusChanged = mServedView != mNextServedView; + final View servedView; + final View nextServedView; + synchronized (mH) { + servedView = getServedViewLocked(); + nextServedView = getNextServedViewLocked(); + } + final boolean focusChanged = servedView != nextServedView; checkFocus(); synchronized (mH) { - if ((mServedView != view && (mServedView == null - || !mServedView.checkInputConnectionProxy(view))) - || mCurrentTextBoxAttribute == null || mCurMethod == null) { + if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null + || mCurMethod == null) { return; } try { @@ -2258,9 +2209,8 @@ public final class InputMethodManager { checkFocus(); synchronized (mH) { - if ((mServedView != view && (mServedView == null - || !mServedView.checkInputConnectionProxy(view))) - || mCurrentTextBoxAttribute == null || mCurMethod == null) { + if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null + || mCurMethod == null) { return; } @@ -2296,9 +2246,8 @@ public final class InputMethodManager { checkFocus(); synchronized (mH) { - if ((mServedView != view && - (mServedView == null || !mServedView.checkInputConnectionProxy(view))) - || mCurrentTextBoxAttribute == null || mCurMethod == null) { + if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null + || mCurMethod == null) { return; } // If immediate bit is set, we will call updateCursorAnchorInfo() even when the data has @@ -2354,9 +2303,8 @@ public final class InputMethodManager { checkFocus(); synchronized (mH) { - if ((mServedView != view && (mServedView == null - || !mServedView.checkInputConnectionProxy(view))) - || mCurrentTextBoxAttribute == null || mCurMethod == null) { + if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null + || mCurMethod == null) { return; } try { @@ -2577,8 +2525,9 @@ public final class InputMethodManager { synchronized (mH) { ViewRootImpl viewRootImpl = targetView != null ? targetView.getViewRootImpl() : null; if (viewRootImpl == null) { - if (mServedView != null) { - viewRootImpl = mServedView.getViewRootImpl(); + final View servedView = getServedViewLocked(); + if (servedView != null) { + viewRootImpl = servedView.getViewRootImpl(); } } if (viewRootImpl != null) { @@ -2759,6 +2708,7 @@ public final class InputMethodManager { /** * Show the settings for enabling subtypes of the specified input method. + * * @param imiId An input method, whose subtypes settings will be shown. If imiId is null, * subtypes of all input methods will be shown. */ @@ -3057,8 +3007,8 @@ public final class InputMethodManager { p.println(" mFullscreenMode=" + mFullscreenMode); p.println(" mCurMethod=" + mCurMethod); p.println(" mCurRootView=" + mCurRootView); - p.println(" mServedView=" + mServedView); - p.println(" mNextServedView=" + mNextServedView); + p.println(" mServedView=" + getServedViewLocked()); + p.println(" mNextServedView=" + getNextServedViewLocked()); p.println(" mServedConnecting=" + mServedConnecting); if (mCurrentTextBoxAttribute != null) { p.println(" mCurrentTextBoxAttribute:"); @@ -3134,6 +3084,8 @@ public final class InputMethodManager { sb.append(",window=" + view.getWindowToken()); sb.append(",displayId=" + view.getContext().getDisplayId()); sb.append(",temporaryDetach=" + view.isTemporarilyDetached()); + sb.append(",hasImeFocus=" + view.hasImeFocus()); + return sb.toString(); } } diff --git a/core/java/android/webkit/PacProcessor.java b/core/java/android/webkit/PacProcessor.java new file mode 100644 index 000000000000..fe8bbb8b6c82 --- /dev/null +++ b/core/java/android/webkit/PacProcessor.java @@ -0,0 +1,58 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.webkit; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; + + +/** + * Class to evaluate PAC scripts. + * @hide + */ + +@SystemApi +public interface PacProcessor { + + /** + * Returns the default PacProcessor instance. + * + * @return the default PacProcessor instance. + */ + @NonNull + static PacProcessor getInstance() { + return WebViewFactory.getProvider().getPacProcessor(); + } + + /** + * Set PAC script to use. + * + * @param script PAC script. + * @return true if PAC script is successfully set. + */ + boolean setProxyScript(@NonNull String script); + + /** + * Gets a list of proxy servers to use. + * @param url The URL being accessed. + * @return a PAC-style semicolon-separated list of valid proxy servers. + * For example: "PROXY xxx.xxx.xxx.xxx:xx; SOCKS yyy.yyy.yyy:yy". + */ + @Nullable + String makeProxyRequest(@NonNull String url); +} diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java index 6a1ed39e25b3..f7c3ec09dd67 100644 --- a/core/java/android/webkit/WebViewFactoryProvider.java +++ b/core/java/android/webkit/WebViewFactoryProvider.java @@ -175,6 +175,15 @@ public interface WebViewFactoryProvider { WebViewDatabase getWebViewDatabase(Context context); /** + * Gets the singleton PacProcessor instance. + * @return the PacProcessor instance + */ + @NonNull + default PacProcessor getPacProcessor() { + throw new UnsupportedOperationException("Not implemented"); + } + + /** * Gets the classloader used to load internal WebView implementation classes. This interface * should only be used by the WebView Support Library. */ diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 20af76b0d5ca..b891af52a887 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -152,7 +152,7 @@ public class Editor { // Specifies whether to allow starting a cursor drag by dragging anywhere over the text. @VisibleForTesting - public static boolean FLAG_ENABLE_CURSOR_DRAG = false; + public static boolean FLAG_ENABLE_CURSOR_DRAG = true; // Specifies whether to use the magnifier when pressing the insertion or selection handles. private static final boolean FLAG_USE_MAGNIFIER = true; @@ -5737,14 +5737,14 @@ public class Editor { private boolean mIsDraggingCursor; public void onTouchEvent(MotionEvent event) { - if (getSelectionController().isCursorBeingModified()) { + if (hasSelectionController() && getSelectionController().isCursorBeingModified()) { return; } switch (event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: - mIsDraggingCursor = false; - break; case MotionEvent.ACTION_MOVE: + if (event.isFromSource(InputDevice.SOURCE_MOUSE)) { + break; + } if (mIsDraggingCursor) { performCursorDrag(event); } else if (FLAG_ENABLE_CURSOR_DRAG diff --git a/core/java/android/widget/EditorTouchState.java b/core/java/android/widget/EditorTouchState.java index 6277afe2f613..b13ca4210612 100644 --- a/core/java/android/widget/EditorTouchState.java +++ b/core/java/android/widget/EditorTouchState.java @@ -173,6 +173,13 @@ public class EditorTouchState { mIsDragCloseToVertical = (4 * deltaXSquared) <= distanceSquared; } } + } else if (action == MotionEvent.ACTION_CANCEL) { + mLastDownMillis = 0; + mLastUpMillis = 0; + mMultiTapStatus = MultiTapStatus.NONE; + mMultiTapInSameArea = false; + mMovedEnoughForDrag = false; + mIsDragCloseToVertical = false; } } diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java index 5731e502ae45..85654931a975 100644 --- a/core/java/android/widget/TextClock.java +++ b/core/java/android/widget/TextClock.java @@ -422,7 +422,7 @@ public class TextClock extends TextView { /** * Update the displayed time if necessary and invalidate the view. */ - public void refresh() { + public void refreshTime() { onTimeChanged(); invalidate(); } diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java index 9bdb4c10067c..d119b2e1992b 100644 --- a/core/java/android/widget/Toast.java +++ b/core/java/android/widget/Toast.java @@ -16,6 +16,8 @@ package android.widget; +import static com.android.internal.util.Preconditions.checkNotNull; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -42,8 +44,12 @@ import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; +import com.android.internal.annotations.GuardedBy; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; /** * A toast is a view containing a quick little message for the user. The toast class @@ -262,6 +268,29 @@ public class Toast { } /** + * Adds a callback to be notified when the toast is shown or hidden. + * + * Note that if the toast is blocked for some reason you won't get a call back. + * + * @see #removeCallback(Callback) + */ + public void addCallback(@NonNull Callback callback) { + checkNotNull(callback); + synchronized (mTN.mCallbacks) { + mTN.mCallbacks.add(callback); + } + } + + /** + * Removes a callback previously added with {@link #addCallback(Callback)}. + */ + public void removeCallback(@NonNull Callback callback) { + synchronized (mTN.mCallbacks) { + mTN.mCallbacks.remove(callback); + } + } + + /** * Gets the LayoutParams for the Toast window. * @hide */ @@ -389,6 +418,9 @@ public class Toast { String mPackageName; + @GuardedBy("mCallbacks") + private final List<Callback> mCallbacks = new ArrayList<>(); + static final long SHORT_DURATION_TIMEOUT = 4000; static final long LONG_DURATION_TIMEOUT = 7000; @@ -449,6 +481,12 @@ public class Toast { }; } + private List<Callback> getCallbacks() { + synchronized (mCallbacks) { + return new ArrayList<>(mCallbacks); + } + } + /** * schedule handleShow into the right thread */ @@ -522,6 +560,9 @@ public class Toast { try { mWM.addView(mView, mParams); trySendAccessibilityEvent(); + for (Callback callback : getCallbacks()) { + callback.onToastShown(); + } } catch (WindowManager.BadTokenException e) { /* ignore */ } @@ -564,8 +605,30 @@ public class Toast { } catch (RemoteException e) { } + for (Callback callback : getCallbacks()) { + callback.onToastHidden(); + } mView = null; } } } + + /** + * Callback object to be called when the toast is shown or hidden. + * + * Callback methods will be called on the looper thread provided on construction. + * + * @see #addCallback(Callback) + */ + public abstract static class Callback { + /** + * Called when the toast is displayed on the screen. + */ + public void onToastShown() {} + + /** + * Called when the toast is hidden. + */ + public void onToastHidden() {} + } } diff --git a/core/java/com/android/ims/internal/uce/common/CapInfo.java b/core/java/com/android/ims/internal/uce/common/CapInfo.java index c45a3a4b8b14..2bb3f1fed927 100644 --- a/core/java/com/android/ims/internal/uce/common/CapInfo.java +++ b/core/java/com/android/ims/internal/uce/common/CapInfo.java @@ -64,6 +64,20 @@ public class CapInfo implements Parcelable { private boolean mRcsIpVideoCallSupported = false; /** RCS IP Video call support . */ private boolean mRcsIpVideoOnlyCallSupported = false; + /** IP Geo location Push using SMS. */ + private boolean mGeoSmsSupported = false; + /** RCS call composer support. */ + private boolean mCallComposerSupported = false; + /** RCS post-call support. */ + private boolean mPostCallSupported = false; + /** Shared map support. */ + private boolean mSharedMapSupported = false; + /** Shared Sketch supported. */ + private boolean mSharedSketchSupported = false; + /** Chatbot communication support. */ + private boolean mChatbotSupported = false; + /** Chatbot role support. */ + private boolean mChatbotRoleSupported = false; /** List of supported extensions. */ private String[] mExts = new String[10]; /** Time used to compute when to query again. */ @@ -386,6 +400,104 @@ public class CapInfo implements Parcelable { this.mRcsIpVideoOnlyCallSupported = rcsIpVideoOnlyCallSupported; } + /** + * Checks whether Geo Push via SMS is supported. + */ + public boolean isGeoSmsSupported() { + return mGeoSmsSupported; + } + + /** + * Sets Geolocation Push via SMS as supported or not supported. + */ + public void setGeoSmsSupported(boolean geoSmsSupported) { + this.mGeoSmsSupported = geoSmsSupported; + } + + /** + * Checks whether RCS call composer is supported. + */ + public boolean isCallComposerSupported() { + return mCallComposerSupported; + } + + /** + * Sets call composer as supported or not supported. + */ + public void setCallComposerSupported(boolean callComposerSupported) { + this.mCallComposerSupported = callComposerSupported; + } + + /** + * Checks whether post call is supported. + */ + public boolean isPostCallSupported(){ + return mPostCallSupported; + } + + /** + * Sets post call as supported or not supported. + */ + public void setPostCallSupported(boolean postCallSupported) { + this.mPostCallSupported = postCallSupported; + } + + /** + * Checks whether shared map is supported. + */ + public boolean isSharedMapSupported() { + return mSharedMapSupported; + } + + /** + * Sets shared map as supported or not supported. + */ + public void setSharedMapSupported(boolean sharedMapSupported) { + this.mSharedMapSupported = sharedMapSupported; + } + + /** + * Checks whether shared sketch is supported. + */ + public boolean isSharedSketchSupported() { + return mSharedSketchSupported; + } + + /** + * Sets shared sketch as supported or not supported. + */ + public void setSharedSketchSupported(boolean sharedSketchSupported) { + this.mSharedSketchSupported = sharedSketchSupported; + } + + /** + * Checks whether chatbot communication is supported. + */ + public boolean isChatbotSupported() { + return mChatbotSupported; + } + + /** + * Sets chatbot communication as supported or not supported. + */ + public void setChatbotSupported(boolean chatbotSupported) { + this.mChatbotSupported = chatbotSupported; + } + + /** + * Checks whether chatbot role is supported. + */ + public boolean isChatbotRoleSupported() { + return mChatbotRoleSupported; + } + + /** + * Sets chatbot role as supported or not supported. + */ + public void setChatbotRoleSupported(boolean chatbotRoleSupported) { + this.mChatbotRoleSupported = chatbotRoleSupported; + } + /** Gets the list of supported extensions. */ public String[] getExts() { return mExts; @@ -434,6 +546,13 @@ public class CapInfo implements Parcelable { dest.writeInt(mGeoPushSupported ? 1 : 0); dest.writeInt(mSmSupported ? 1 : 0); dest.writeInt(mFullSnFGroupChatSupported ? 1 : 0); + dest.writeInt(mGeoSmsSupported ? 1 : 0); + dest.writeInt(mCallComposerSupported ? 1 : 0); + dest.writeInt(mPostCallSupported ? 1 : 0); + dest.writeInt(mSharedMapSupported ? 1 : 0); + dest.writeInt(mSharedSketchSupported ? 1 : 0); + dest.writeInt(mChatbotSupported ? 1 : 0); + dest.writeInt(mChatbotRoleSupported ? 1 : 0); dest.writeInt(mRcsIpVoiceCallSupported ? 1 : 0); dest.writeInt(mRcsIpVideoCallSupported ? 1 : 0); @@ -476,6 +595,13 @@ public class CapInfo implements Parcelable { mGeoPushSupported = (source.readInt() == 0) ? false : true; mSmSupported = (source.readInt() == 0) ? false : true; mFullSnFGroupChatSupported = (source.readInt() == 0) ? false : true; + mGeoSmsSupported = (source.readInt() == 0) ? false : true; + mCallComposerSupported = (source.readInt() == 0) ? false : true; + mPostCallSupported = (source.readInt() == 0) ? false : true; + mSharedMapSupported = (source.readInt() == 0) ? false : true; + mSharedSketchSupported = (source.readInt() == 0) ? false : true; + mChatbotSupported = (source.readInt() == 0) ? false : true; + mChatbotRoleSupported = (source.readInt() == 0) ? false : true; mRcsIpVoiceCallSupported = (source.readInt() == 0) ? false : true; mRcsIpVideoCallSupported = (source.readInt() == 0) ? false : true; diff --git a/core/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.java b/core/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.java index a50a22f68fa0..fdff86f9669f 100644 --- a/core/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.java +++ b/core/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.java @@ -47,6 +47,10 @@ public class PresPublishTriggerType implements Parcelable { public static final int UCE_PRES_PUBLISH_TRIGGER_MOVE_TO_IWLAN = 8; /** Trigger is unknown. */ public static final int UCE_PRES_PUBLISH_TRIGGER_UNKNOWN = 9; + /** Move to 5G NR with VoPS disabled. */ + public static final int UCE_PRES_PUBLISH_TRIGGER_MOVE_TO_NR5G_VOPS_DISABLED = 10; + /** Move to 5G NR with VoPS enabled. */ + public static final int UCE_PRES_PUBLISH_TRIGGER_MOVE_TO_NR5G_VOPS_ENABLED = 11; @@ -113,4 +117,4 @@ public class PresPublishTriggerType implements Parcelable { public void readFromParcel(Parcel source) { mPublishTriggerType = source.readInt(); } -}
\ No newline at end of file +} diff --git a/core/java/com/android/ims/internal/uce/uceservice/ImsUceManager.java b/core/java/com/android/ims/internal/uce/uceservice/ImsUceManager.java index 4b0b098d4e5b..9aee879f21da 100644 --- a/core/java/com/android/ims/internal/uce/uceservice/ImsUceManager.java +++ b/core/java/com/android/ims/internal/uce/uceservice/ImsUceManager.java @@ -18,16 +18,9 @@ package com.android.ims.internal.uce.uceservice; import android.content.Context; import android.content.Intent; - -import android.os.Handler; -import android.os.HandlerThread; import android.os.IBinder; -import android.os.Message; -import android.os.ServiceManager; import android.os.RemoteException; - -import java.util.HashMap; -import android.util.Log; +import android.os.ServiceManager; /** * ImsUceManager Declaration @@ -49,55 +42,25 @@ public class ImsUceManager { private IUceService mUceService = null; private UceServiceDeathRecipient mDeathReceipient = new UceServiceDeathRecipient(); private Context mContext; - private int mPhoneId; - /** - * Stores the UceManager instaces of Clients identified by - * phoneId - * @hide - */ - private static HashMap<Integer, ImsUceManager> sUceManagerInstances = - new HashMap<Integer, ImsUceManager>(); + private static final Object sLock = new Object(); + private static ImsUceManager sUceManager; public static final String ACTION_UCE_SERVICE_UP = "com.android.ims.internal.uce.UCE_SERVICE_UP"; public static final String ACTION_UCE_SERVICE_DOWN = "com.android.ims.internal.uce.UCE_SERVICE_DOWN"; - /** Uce Service status received in IUceListener.setStatus() - * callback - * @hide - */ - public static final int UCE_SERVICE_STATUS_FAILURE = 0; - /** indicate UI to call Presence/Options API. */ - public static final int UCE_SERVICE_STATUS_ON = 1; - /** Indicate UI destroy Presence/Options */ - public static final int UCE_SERVICE_STATUS_CLOSED = 2; - /** Service up and trying to register for network events */ - public static final int UCE_SERVICE_STATUS_READY = 3; - - /** - * Part of the ACTION_UCE_SERVICE_UP or _DOWN intents. A long - * value; the phone ID corresponding to the IMS service coming up or down. - * Internal use only. - * @hide - */ - public static final String EXTRA_PHONE_ID = "android:phone_id"; - /** * Gets the instance of UCE Manager * @hide */ - public static ImsUceManager getInstance(Context context, int phoneId) { - //if (DBG) Log.d (LOG_TAG, "GetInstance Called"); - synchronized (sUceManagerInstances) { - if (sUceManagerInstances.containsKey(phoneId)) { - return sUceManagerInstances.get(phoneId); - } else { - ImsUceManager uceMgr = new ImsUceManager(context, phoneId); - sUceManagerInstances.put(phoneId, uceMgr); - return uceMgr; + public static ImsUceManager getInstance(Context context) { + synchronized (sLock) { + if (sUceManager == null && context != null) { + sUceManager = new ImsUceManager(context); } + return sUceManager; } } @@ -105,10 +68,9 @@ public class ImsUceManager { * Constructor * @hide */ - private ImsUceManager(Context context, int phoneId) { + private ImsUceManager(Context context) { //if (DBG) Log.d (LOG_TAG, "Constructor"); mContext = context; - mPhoneId = phoneId; createUceService(true); } @@ -129,7 +91,7 @@ public class ImsUceManager { * Gets the UCE service name * @hide */ - private String getUceServiceName(int phoneId) { + private String getUceServiceName() { return UCE_SERVICE; } @@ -143,14 +105,14 @@ public class ImsUceManager { public void createUceService(boolean checkService) { //if (DBG) Log.d (LOG_TAG, "CreateUceService Called"); if (checkService) { - IBinder binder = ServiceManager.checkService(getUceServiceName(mPhoneId)); + IBinder binder = ServiceManager.checkService(getUceServiceName()); if (binder == null) { //if (DBG)Log.d (LOG_TAG, "Unable to find IBinder"); return; } } - IBinder b = ServiceManager.getService(getUceServiceName(mPhoneId)); + IBinder b = ServiceManager.getService(getUceServiceName()); if (b != null) { try { @@ -174,12 +136,10 @@ public class ImsUceManager { private class UceServiceDeathRecipient implements IBinder.DeathRecipient { @Override public void binderDied() { - //if (DBG) Log.d (LOG_TAG, "found IBinder/IUceService Service Died"); mUceService = null; if (mContext != null) { Intent intent = new Intent(ACTION_UCE_SERVICE_DOWN); - intent.putExtra(EXTRA_PHONE_ID, mPhoneId); mContext.sendBroadcast(new Intent(intent)); } } diff --git a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java index de204badfd0d..457c0331c141 100644 --- a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java +++ b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java @@ -19,6 +19,15 @@ import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTT import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY; import static android.view.accessibility.AccessibilityManager.ShortcutType; +import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME; +import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME; +import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME; +import static com.android.internal.app.AccessibilityButtonChooserActivity.WhiteListingFeatureElementIndex.COMPONENT_ID; +import static com.android.internal.app.AccessibilityButtonChooserActivity.WhiteListingFeatureElementIndex.FRAGMENT_TYPE; +import static com.android.internal.app.AccessibilityButtonChooserActivity.WhiteListingFeatureElementIndex.ICON_ID; +import static com.android.internal.app.AccessibilityButtonChooserActivity.WhiteListingFeatureElementIndex.LABEL_ID; +import static com.android.internal.app.AccessibilityButtonChooserActivity.WhiteListingFeatureElementIndex.SETTINGS_KEY; + import android.accessibilityservice.AccessibilityServiceInfo; import android.annotation.IntDef; import android.annotation.NonNull; @@ -63,20 +72,19 @@ import java.util.StringJoiner; * Activity used to display and persist a service or feature target for the Accessibility button. */ public class AccessibilityButtonChooserActivity extends Activity { - - private static final String MAGNIFICATION_COMPONENT_ID = - "com.android.server.accessibility.MagnificationController"; - private static final char SERVICES_SEPARATOR = ':'; private static final TextUtils.SimpleStringSplitter sStringColonSplitter = new TextUtils.SimpleStringSplitter(SERVICES_SEPARATOR); + @UserShortcutType private static final int ACCESSIBILITY_BUTTON_USER_TYPE = convertToUserType( ACCESSIBILITY_BUTTON); + @UserShortcutType private static final int ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE = convertToUserType( ACCESSIBILITY_SHORTCUT_KEY); + @ShortcutType private int mShortcutType; - private List<AccessibilityButtonTarget> mTargets = new ArrayList<>(); + private final List<AccessibilityButtonTarget> mTargets = new ArrayList<>(); private AlertDialog mAlertDialog; private TargetAdapter mTargetAdapter; @@ -99,7 +107,7 @@ public class AccessibilityButtonChooserActivity extends Activity { UserShortcutType.TRIPLETAP, }) /** Denotes the user shortcut type. */ - public @interface UserShortcutType { + private @interface UserShortcutType { int DEFAULT = 0; int SOFTWARE = 1; // 1 << 0 int HARDWARE = 2; // 1 << 1 @@ -122,7 +130,7 @@ public class AccessibilityButtonChooserActivity extends Activity { AccessibilityServiceFragmentType.INTUITIVE, AccessibilityServiceFragmentType.BOUNCE, }) - public @interface AccessibilityServiceFragmentType { + private @interface AccessibilityServiceFragmentType { int LEGACY = 0; int INVISIBLE = 1; int INTUITIVE = 2; @@ -140,11 +148,61 @@ public class AccessibilityButtonChooserActivity extends Activity { ShortcutMenuMode.LAUNCH, ShortcutMenuMode.EDIT, }) - public @interface ShortcutMenuMode { + private @interface ShortcutMenuMode { int LAUNCH = 0; int EDIT = 1; } + /** + * Annotation for align the element index of white listing feature + * {@code WHITE_LISTING_FEATURES}. + * + * {@code COMPONENT_ID} is to get the service component name. + * {@code LABEL_ID} is to get the service label text. + * {@code ICON_ID} is to get the service icon. + * {@code FRAGMENT_TYPE} is to get the service fragment type. + * {@code SETTINGS_KEY} is to get the service settings key. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + WhiteListingFeatureElementIndex.COMPONENT_ID, + WhiteListingFeatureElementIndex.LABEL_ID, + WhiteListingFeatureElementIndex.ICON_ID, + WhiteListingFeatureElementIndex.FRAGMENT_TYPE, + WhiteListingFeatureElementIndex.SETTINGS_KEY, + }) + @interface WhiteListingFeatureElementIndex { + int COMPONENT_ID = 0; + int LABEL_ID = 1; + int ICON_ID = 2; + int FRAGMENT_TYPE = 3; + int SETTINGS_KEY = 4; + } + + private static final String[][] WHITE_LISTING_FEATURES = { + { + COLOR_INVERSION_COMPONENT_NAME.flattenToString(), + String.valueOf(R.string.color_inversion_feature_name), + String.valueOf(R.drawable.ic_accessibility_color_inversion), + String.valueOf(AccessibilityServiceFragmentType.INTUITIVE), + Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, + }, + { + DALTONIZER_COMPONENT_NAME.flattenToString(), + String.valueOf(R.string.color_correction_feature_name), + String.valueOf(R.drawable.ic_accessibility_color_correction), + String.valueOf(AccessibilityServiceFragmentType.INTUITIVE), + Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, + }, + { + MAGNIFICATION_CONTROLLER_NAME, + String.valueOf(R.string.accessibility_magnification_chooser_text), + String.valueOf(R.drawable.ic_accessibility_magnification), + String.valueOf(AccessibilityServiceFragmentType.INVISIBLE), + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, + }, + }; + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -156,9 +214,14 @@ public class AccessibilityButtonChooserActivity extends Activity { mShortcutType = getIntent().getIntExtra(AccessibilityManager.EXTRA_SHORTCUT_TYPE, ACCESSIBILITY_BUTTON); + if ((mShortcutType != ACCESSIBILITY_BUTTON) + && (mShortcutType != ACCESSIBILITY_SHORTCUT_KEY)) { + throw new IllegalStateException("Unexpected shortcut type: " + mShortcutType); + } + mTargets.addAll(getServiceTargets(this, mShortcutType)); - mTargetAdapter = new TargetAdapter(mTargets); + mTargetAdapter = new TargetAdapter(mTargets, mShortcutType); mAlertDialog = new AlertDialog.Builder(this) .setAdapter(mTargetAdapter, /* listener= */ null) .setPositiveButton( @@ -199,6 +262,20 @@ public class AccessibilityButtonChooserActivity extends Activity { private static List<AccessibilityButtonTarget> getServiceTargets(@NonNull Context context, @ShortcutType int shortcutType) { + final List<AccessibilityButtonTarget> targets = new ArrayList<>(); + targets.addAll(getAccessibilityServiceTargets(context)); + targets.addAll(getWhiteListingServiceTargets(context)); + + final AccessibilityManager ams = (AccessibilityManager) context.getSystemService( + Context.ACCESSIBILITY_SERVICE); + final List<String> requiredTargets = ams.getAccessibilityShortcutTargets(shortcutType); + targets.removeIf(target -> !requiredTargets.contains(target.getId())); + + return targets; + } + + private static List<AccessibilityButtonTarget> getAccessibilityServiceTargets( + @NonNull Context context) { final AccessibilityManager ams = (AccessibilityManager) context.getSystemService( Context.ACCESSIBILITY_SERVICE); final List<AccessibilityServiceInfo> installedServices = @@ -209,29 +286,74 @@ public class AccessibilityButtonChooserActivity extends Activity { final List<AccessibilityButtonTarget> targets = new ArrayList<>(installedServices.size()); for (AccessibilityServiceInfo info : installedServices) { - if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) { - targets.add(new AccessibilityButtonTarget(context, info)); - } + targets.add(new AccessibilityButtonTarget(context, info)); } - final List<String> requiredTargets = ams.getAccessibilityShortcutTargets(shortcutType); - targets.removeIf(target -> !requiredTargets.contains(target.getId())); + return targets; + } + + private static List<AccessibilityButtonTarget> getWhiteListingServiceTargets( + @NonNull Context context) { + final List<AccessibilityButtonTarget> targets = new ArrayList<>(); - // TODO(b/146815874): Will replace it with white list services. - if (Settings.Secure.getInt(context.getContentResolver(), - Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, 0) == 1) { - final AccessibilityButtonTarget magnificationTarget = new AccessibilityButtonTarget( + for (int i = 0; i < WHITE_LISTING_FEATURES.length; i++) { + final AccessibilityButtonTarget target = new AccessibilityButtonTarget( context, - MAGNIFICATION_COMPONENT_ID, - R.string.accessibility_magnification_chooser_text, - R.drawable.ic_accessibility_magnification, - AccessibilityServiceFragmentType.INTUITIVE); - targets.add(magnificationTarget); + WHITE_LISTING_FEATURES[i][COMPONENT_ID], + Integer.parseInt(WHITE_LISTING_FEATURES[i][LABEL_ID]), + Integer.parseInt(WHITE_LISTING_FEATURES[i][ICON_ID]), + Integer.parseInt(WHITE_LISTING_FEATURES[i][FRAGMENT_TYPE])); + targets.add(target); } return targets; } + private static boolean isWhiteListingServiceEnabled(@NonNull Context context, + AccessibilityButtonTarget target) { + + for (int i = 0; i < WHITE_LISTING_FEATURES.length; i++) { + if (WHITE_LISTING_FEATURES[i][COMPONENT_ID].equals(target.getId())) { + return Settings.Secure.getInt(context.getContentResolver(), + WHITE_LISTING_FEATURES[i][SETTINGS_KEY], + /* settingsValueOff */ 0) == /* settingsValueOn */ 1; + } + } + + return false; + } + + private static boolean isWhiteListingService(String componentId) { + for (int i = 0; i < WHITE_LISTING_FEATURES.length; i++) { + if (WHITE_LISTING_FEATURES[i][COMPONENT_ID].equals(componentId)) { + return true; + } + } + + return false; + } + + private void setWhiteListingServiceEnabled(String componentId, int settingsValue) { + for (int i = 0; i < WHITE_LISTING_FEATURES.length; i++) { + if (WHITE_LISTING_FEATURES[i][COMPONENT_ID].equals(componentId)) { + Settings.Secure.putInt(getContentResolver(), + WHITE_LISTING_FEATURES[i][SETTINGS_KEY], settingsValue); + return; + } + } + } + + private void disableService(ComponentName componentName) { + final String componentId = componentName.flattenToString(); + + if (isWhiteListingService(componentId)) { + setWhiteListingServiceEnabled(componentName.flattenToString(), + /* settingsValueOff */ 0); + } else { + setAccessibilityServiceState(this, componentName, /* enabled= */ false); + } + } + private static class ViewHolder { ImageView mIconView; TextView mLabelView; @@ -243,16 +365,21 @@ public class AccessibilityButtonChooserActivity extends Activity { private static class TargetAdapter extends BaseAdapter { @ShortcutMenuMode private int mShortcutMenuMode = ShortcutMenuMode.LAUNCH; + @ShortcutType + private int mShortcutButtonType; private List<AccessibilityButtonTarget> mButtonTargets; - TargetAdapter(List<AccessibilityButtonTarget> targets) { + TargetAdapter(List<AccessibilityButtonTarget> targets, + @ShortcutType int shortcutButtonType) { this.mButtonTargets = targets; + this.mShortcutButtonType = shortcutButtonType; } - void setShortcutMenuMode(int shortcutMenuMode) { + void setShortcutMenuMode(@ShortcutMenuMode int shortcutMenuMode) { mShortcutMenuMode = shortcutMenuMode; } + @ShortcutMenuMode int getShortcutMenuMode() { return mShortcutMenuMode; } @@ -327,14 +454,16 @@ public class AccessibilityButtonChooserActivity extends Activity { private void updateLegacyActionItemVisibility(@NonNull Context context, @NonNull ViewHolder holder) { - final boolean isEditMenuMode = (mShortcutMenuMode == ShortcutMenuMode.EDIT); + final boolean isLaunchMenuMode = (mShortcutMenuMode == ShortcutMenuMode.LAUNCH); + final boolean isHardwareButtonTriggered = + (mShortcutButtonType == ACCESSIBILITY_SHORTCUT_KEY); - holder.mLabelView.setEnabled(!isEditMenuMode); - holder.mViewItem.setEnabled(!isEditMenuMode); + holder.mLabelView.setEnabled(isLaunchMenuMode || isHardwareButtonTriggered); + holder.mViewItem.setEnabled(isLaunchMenuMode || isHardwareButtonTriggered); holder.mViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item)); holder.mViewItem.setVisibility(View.VISIBLE); holder.mSwitchItem.setVisibility(View.GONE); - holder.mItemContainer.setVisibility(isEditMenuMode ? View.VISIBLE : View.GONE); + holder.mItemContainer.setVisibility(isLaunchMenuMode ? View.GONE : View.VISIBLE); } private void updateInvisibleActionItemVisibility(@NonNull Context context, @@ -350,11 +479,14 @@ public class AccessibilityButtonChooserActivity extends Activity { private void updateIntuitiveActionItemVisibility(@NonNull Context context, @NonNull ViewHolder holder, AccessibilityButtonTarget target) { final boolean isEditMenuMode = (mShortcutMenuMode == ShortcutMenuMode.EDIT); + final boolean isServiceEnabled = isWhiteListingService(target.getId()) + ? isWhiteListingServiceEnabled(context, target) + : isAccessibilityServiceEnabled(context, target); holder.mViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item)); holder.mViewItem.setVisibility(isEditMenuMode ? View.VISIBLE : View.GONE); holder.mSwitchItem.setVisibility(isEditMenuMode ? View.GONE : View.VISIBLE); - holder.mSwitchItem.setChecked(!isEditMenuMode && isServiceEnabled(context, target)); + holder.mSwitchItem.setChecked(!isEditMenuMode && isServiceEnabled); holder.mItemContainer.setVisibility(View.VISIBLE); } @@ -411,7 +543,7 @@ public class AccessibilityButtonChooserActivity extends Activity { } } - private static boolean isServiceEnabled(@NonNull Context context, + private static boolean isAccessibilityServiceEnabled(@NonNull Context context, AccessibilityButtonTarget target) { final AccessibilityManager ams = (AccessibilityManager) context.getSystemService( Context.ACCESSIBILITY_SERVICE); @@ -429,26 +561,78 @@ public class AccessibilityButtonChooserActivity extends Activity { } private void onTargetSelected(AdapterView<?> parent, View view, int position, long id) { - Settings.Secure.putString(getContentResolver(), - Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT, - mTargets.get(position).getId()); - // TODO(b/146969684): notify accessibility button clicked. + final AccessibilityButtonTarget target = mTargets.get(position); + switch (target.getFragmentType()) { + case AccessibilityServiceFragmentType.LEGACY: + onLegacyTargetSelected(target); + break; + case AccessibilityServiceFragmentType.INVISIBLE: + onInvisibleTargetSelected(target); + break; + case AccessibilityServiceFragmentType.INTUITIVE: + onIntuitiveTargetSelected(target); + break; + case AccessibilityServiceFragmentType.BOUNCE: + // Do nothing + break; + default: + throw new IllegalStateException("Unexpected fragment type"); + } + mAlertDialog.dismiss(); } + private void onLegacyTargetSelected(AccessibilityButtonTarget target) { + if (mShortcutType == ACCESSIBILITY_BUTTON) { + final AccessibilityManager ams = (AccessibilityManager) getSystemService( + Context.ACCESSIBILITY_SERVICE); + ams.notifyAccessibilityButtonClicked(getDisplayId(), target.getId()); + } else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) { + switchServiceState(target); + } + } + + private void onInvisibleTargetSelected(AccessibilityButtonTarget target) { + final AccessibilityManager ams = (AccessibilityManager) getSystemService( + Context.ACCESSIBILITY_SERVICE); + ams.notifyAccessibilityButtonClicked(getDisplayId(), target.getId()); + } + + private void onIntuitiveTargetSelected(AccessibilityButtonTarget target) { + switchServiceState(target); + } + + private void switchServiceState(AccessibilityButtonTarget target) { + final ComponentName componentName = + ComponentName.unflattenFromString(target.getId()); + final String componentId = componentName.flattenToString(); + + if (isWhiteListingService(componentId)) { + setWhiteListingServiceEnabled(componentId, + isWhiteListingServiceEnabled(this, target) + ? /* settingsValueOff */ 0 + : /* settingsValueOn */ 1); + } else { + setAccessibilityServiceState(this, componentName, + /* enabled= */!isAccessibilityServiceEnabled(this, target)); + } + } + private void onTargetDeleted(AdapterView<?> parent, View view, int position, long id) { final AccessibilityButtonTarget target = mTargets.get(position); final ComponentName targetComponentName = ComponentName.unflattenFromString(target.getId()); switch (target.getFragmentType()) { + case AccessibilityServiceFragmentType.LEGACY: + onLegacyTargetDeleted(targetComponentName); + break; case AccessibilityServiceFragmentType.INVISIBLE: onInvisibleTargetDeleted(targetComponentName); break; case AccessibilityServiceFragmentType.INTUITIVE: onIntuitiveTargetDeleted(targetComponentName); break; - case AccessibilityServiceFragmentType.LEGACY: case AccessibilityServiceFragmentType.BOUNCE: // Do nothing break; @@ -464,23 +648,27 @@ public class AccessibilityButtonChooserActivity extends Activity { } } + private void onLegacyTargetDeleted(ComponentName componentName) { + if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) { + optOutValueFromSettings(this, ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName); + } + } + private void onInvisibleTargetDeleted(ComponentName componentName) { if (mShortcutType == ACCESSIBILITY_BUTTON) { optOutValueFromSettings(this, ACCESSIBILITY_BUTTON_USER_TYPE, componentName); if (!hasValueInSettings(this, ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName)) { - setAccessibilityServiceState(this, componentName, /* enabled= */ false); + disableService(componentName); } } else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) { optOutValueFromSettings(this, ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName); if (!hasValueInSettings(this, ACCESSIBILITY_BUTTON_USER_TYPE, componentName)) { - setAccessibilityServiceState(this, componentName, /* enabled= */ false); + disableService(componentName); } - } else { - throw new IllegalArgumentException("Unsupported shortcut type:" + mShortcutType); } } @@ -489,8 +677,6 @@ public class AccessibilityButtonChooserActivity extends Activity { optOutValueFromSettings(this, ACCESSIBILITY_BUTTON_USER_TYPE, componentName); } else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) { optOutValueFromSettings(this, ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName); - } else { - throw new IllegalArgumentException("Unsupported shortcut type:" + mShortcutType); } } @@ -603,7 +789,7 @@ public class AccessibilityButtonChooserActivity extends Activity { * @param componentName The component name that need to be opted out from Settings. */ private void optOutValueFromSettings( - Context context, int shortcutType, ComponentName componentName) { + Context context, @UserShortcutType int shortcutType, ComponentName componentName) { final StringJoiner joiner = new StringJoiner(String.valueOf(SERVICES_SEPARATOR)); final String targetsKey = convertToKey(shortcutType); final String targetsValue = Settings.Secure.getString(context.getContentResolver(), diff --git a/core/java/com/android/internal/app/BlockedAppActivity.java b/core/java/com/android/internal/app/BlockedAppActivity.java new file mode 100644 index 000000000000..fbdbbfb06b78 --- /dev/null +++ b/core/java/com/android/internal/app/BlockedAppActivity.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.app; + +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Slog; + +import com.android.internal.R; + +/** + * A dialog shown to the user when they try to launch an app that is not allowed in lock task + * mode. The intent to start this activity must be created with the static factory method provided + * below. + */ +public class BlockedAppActivity extends AlertActivity { + + private static final String TAG = "BlockedAppActivity"; + private static final String PACKAGE_NAME = "com.android.internal.app"; + private static final String EXTRA_BLOCKED_PACKAGE = PACKAGE_NAME + ".extra.BLOCKED_PACKAGE"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Intent intent = getIntent(); + int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, /* defaultValue= */ -1); + if (userId < 0) { + Slog.wtf(TAG, "Invalid user: " + userId); + finish(); + return; + } + + String packageName = intent.getStringExtra(EXTRA_BLOCKED_PACKAGE); + if (TextUtils.isEmpty(packageName)) { + Slog.wtf(TAG, "Invalid package: " + packageName); + finish(); + return; + } + + CharSequence appLabel = getAppLabel(userId, packageName); + + mAlertParams.mTitle = getString(R.string.app_blocked_title); + mAlertParams.mMessage = getString(R.string.app_blocked_message, appLabel); + mAlertParams.mPositiveButtonText = getString(android.R.string.ok); + setupAlert(); + } + + private CharSequence getAppLabel(int userId, String packageName) { + PackageManager pm = getPackageManager(); + try { + ApplicationInfo aInfo = + pm.getApplicationInfoAsUser(packageName, /* flags= */ 0, userId); + return aInfo.loadLabel(pm); + } catch (PackageManager.NameNotFoundException ne) { + Slog.e(TAG, "Package " + packageName + " not found", ne); + } + return packageName; + } + + + /** Creates an intent that launches {@link BlockedAppActivity}. */ + public static Intent createIntent(int userId, String packageName) { + return new Intent() + .setClassName("android", BlockedAppActivity.class.getName()) + .putExtra(Intent.EXTRA_USER_ID, userId) + .putExtra(EXTRA_BLOCKED_PACKAGE, packageName); + } +} diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index 9fbc1b74c9ae..de64573d1e24 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -357,6 +357,13 @@ public final class SystemUiDeviceConfigFlags { */ public static final String SCREENSHOT_SCROLLING_ENABLED = "enable_screenshot_scrolling"; + // Flags related to Nav Bar + + /** + * (boolean) Whether to force the Nav Bar handle to remain opaque. + */ + public static final String NAV_BAR_HANDLE_FORCE_OPAQUE = "nav_bar_handle_force_opaque"; + private SystemUiDeviceConfigFlags() { } } diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java index 221cd6d8a5c3..ef9b3d1021ef 100644 --- a/core/java/com/android/internal/content/FileSystemProvider.java +++ b/core/java/com/android/internal/content/FileSystemProvider.java @@ -561,7 +561,7 @@ public abstract class FileSystemProvider extends DocumentsProvider { flags |= Document.FLAG_SUPPORTS_MOVE; if (shouldBlockFromTree(docId)) { - flags |= Document.FLAG_DIR_BLOCKS_TREE; + flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE; } } else { diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl index 6fd271c5490f..0f50596f935d 100644 --- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl +++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl @@ -16,6 +16,7 @@ package com.android.internal.telephony; +import android.telephony.BarringInfo; import android.telephony.CallAttributes; import android.telephony.CellIdentity; import android.telephony.CellInfo; @@ -64,4 +65,5 @@ oneway interface IPhoneStateListener { void onImsCallDisconnectCauseChanged(in ImsReasonInfo imsReasonInfo); void onRegistrationFailed(in CellIdentity cellIdentity, String chosenPlmn, int domain, int causeCode, int additionalCauseCode); + void onBarringInfoChanged(in BarringInfo barringInfo); } diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl index 8e97ae1e4bd1..47752c5b2d94 100644 --- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl +++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl @@ -19,6 +19,7 @@ package com.android.internal.telephony; import android.content.Intent; import android.net.LinkProperties; import android.net.NetworkCapabilities; +import android.telephony.BarringInfo; import android.telephony.CallQuality; import android.telephony.CellIdentity; import android.telephony.CellInfo; @@ -99,4 +100,5 @@ interface ITelephonyRegistry { void notifyImsDisconnectCause(int subId, in ImsReasonInfo imsReasonInfo); 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); } diff --git a/core/java/com/android/server/BootReceiver.java b/core/java/com/android/server/BootReceiver.java index 13bfc1bf72b8..31b5e491d0e5 100644 --- a/core/java/com/android/server/BootReceiver.java +++ b/core/java/com/android/server/BootReceiver.java @@ -44,21 +44,21 @@ import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.XmlUtils; import com.android.server.DropboxLogTags; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; -import java.io.FileNotFoundException; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Iterator; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlSerializer; - /** * Performs a number of miscellaneous, non-system-critical actions * after the system has finished booting. @@ -424,7 +424,23 @@ public class BootReceiver extends BroadcastReceiver { for (String propPostfix : MOUNT_DURATION_PROPS_POSTFIX) { int duration = SystemProperties.getInt("ro.boottime.init.mount_all." + propPostfix, 0); if (duration != 0) { - MetricsLogger.histogram(null, "boot_mount_all_duration_" + propPostfix, duration); + int eventType; + switch (propPostfix) { + case "early": + eventType = StatsLog.BOOT_TIME_EVENT_DURATION__EVENT__MOUNT_EARLY_DURATION; + break; + case "default": + eventType = + StatsLog.BOOT_TIME_EVENT_DURATION__EVENT__MOUNT_DEFAULT_DURATION; + break; + case "late": + eventType = StatsLog.BOOT_TIME_EVENT_DURATION__EVENT__MOUNT_LATE_DURATION; + break; + default: + continue; + } + StatsLog.write(StatsLog.BOOT_TIME_EVENT_DURATION_REPORTED, eventType, + duration); } } } @@ -555,16 +571,19 @@ public class BootReceiver extends BroadcastReceiver { Pattern pattern = Pattern.compile(LAST_SHUTDOWN_TIME_PATTERN, Pattern.MULTILINE); Matcher matcher = pattern.matcher(lines); if (matcher.find()) { - MetricsLogger.histogram(null, "boot_fs_shutdown_duration", + StatsLog.write(StatsLog.BOOT_TIME_EVENT_DURATION_REPORTED, + StatsLog.BOOT_TIME_EVENT_DURATION__EVENT__SHUTDOWN_DURATION, Integer.parseInt(matcher.group(1))); - MetricsLogger.histogram(null, "boot_fs_shutdown_umount_stat", + StatsLog.write(StatsLog.BOOT_TIME_EVENT_ERROR_CODE_REPORTED, + StatsLog.BOOT_TIME_EVENT_ERROR_CODE__EVENT__SHUTDOWN_UMOUNT_STAT, Integer.parseInt(matcher.group(2))); Slog.i(TAG, "boot_fs_shutdown," + matcher.group(1) + "," + matcher.group(2)); } else { // not found // This can happen when a device has too much kernel log after file system unmount // ,exceeding maxReadSize. And having that much kernel logging can affect overall // performance as well. So it is better to fix the kernel to reduce the amount of log. - MetricsLogger.histogram(null, "boot_fs_shutdown_umount_stat", + StatsLog.write(StatsLog.BOOT_TIME_EVENT_ERROR_CODE_REPORTED, + StatsLog.BOOT_TIME_EVENT_ERROR_CODE__EVENT__SHUTDOWN_UMOUNT_STAT, UMOUNT_STATUS_NOT_AVAILABLE); Slog.w(TAG, "boot_fs_shutdown, string not found"); } @@ -674,7 +693,11 @@ public class BootReceiver extends BroadcastReceiver { return; } stat = fixFsckFsStat(partition, stat, lines, startLineNumber, endLineNumber); - MetricsLogger.histogram(null, "boot_fs_stat_" + partition, stat); + if ("userdata".equals(partition) || "data".equals(partition)) { + StatsLog.write(StatsLog.BOOT_TIME_EVENT_ERROR_CODE_REPORTED, + StatsLog.BOOT_TIME_EVENT_ERROR_CODE__EVENT__FS_MGR_FS_STAT_DATA_PARTITION, + stat); + } Slog.i(TAG, "fs_stat, partition:" + partition + " stat:0x" + Integer.toHexString(stat)); } diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 6e6746f16748..a2f514a85ff8 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -344,7 +344,6 @@ cc_library_static { cppflags: ["-Wno-conversion-null"], srcs: [ - "android/graphics/apex/android_bitmap.cpp", "android/graphics/apex/android_matrix.cpp", "android/graphics/apex/android_paint.cpp", "android/graphics/apex/android_region.cpp", @@ -430,6 +429,7 @@ cc_library_static { android: { srcs: [ // sources that depend on android only libraries "android/graphics/apex/android_canvas.cpp", + "android/graphics/apex/android_bitmap.cpp", "android/graphics/apex/renderthread.cpp", "android/graphics/apex/jni_runtime.cpp", diff --git a/core/jni/android/graphics/apex/android_bitmap.cpp b/core/jni/android/graphics/apex/android_bitmap.cpp index 90cc98699827..6a3c01efe98c 100644 --- a/core/jni/android/graphics/apex/android_bitmap.cpp +++ b/core/jni/android/graphics/apex/android_bitmap.cpp @@ -122,6 +122,98 @@ AndroidBitmapInfo ABitmap_getInfo(ABitmap* bitmapHandle) { return getInfo(bitmap->info(), bitmap->rowBytes()); } +static bool nearlyEqual(float a, float b) { + // By trial and error, this is close enough to match for the ADataSpaces we + // compare for. + return ::fabs(a - b) < .002f; +} + +static bool nearlyEqual(const skcms_TransferFunction& x, const skcms_TransferFunction& y) { + return nearlyEqual(x.g, y.g) + && nearlyEqual(x.a, y.a) + && nearlyEqual(x.b, y.b) + && nearlyEqual(x.c, y.c) + && nearlyEqual(x.d, y.d) + && nearlyEqual(x.e, y.e) + && nearlyEqual(x.f, y.f); +} + +static bool nearlyEqual(const skcms_Matrix3x3& x, const skcms_Matrix3x3& y) { + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + if (!nearlyEqual(x.vals[i][j], y.vals[i][j])) return false; + } + } + return true; +} + +static constexpr skcms_TransferFunction k2Dot6 = {2.6f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}; + +// Skia's SkNamedGamut::kDCIP3 is based on a white point of D65. This gamut +// matches the white point used by ColorSpace.Named.DCIP3. +static constexpr skcms_Matrix3x3 kDCIP3 = {{ + {0.486143, 0.323835, 0.154234}, + {0.226676, 0.710327, 0.0629966}, + {0.000800549, 0.0432385, 0.78275}, +}}; + +ADataSpace ABitmap_getDataSpace(ABitmap* bitmapHandle) { + Bitmap* bitmap = TypeCast::toBitmap(bitmapHandle); + const SkImageInfo& info = bitmap->info(); + SkColorSpace* colorSpace = info.colorSpace(); + if (!colorSpace) { + return ADATASPACE_UNKNOWN; + } + + if (colorSpace->isSRGB()) { + if (info.colorType() == kRGBA_F16_SkColorType) { + return ADATASPACE_SCRGB; + } + return ADATASPACE_SRGB; + } + + skcms_TransferFunction fn; + LOG_ALWAYS_FATAL_IF(!colorSpace->isNumericalTransferFn(&fn)); + + skcms_Matrix3x3 gamut; + LOG_ALWAYS_FATAL_IF(!colorSpace->toXYZD50(&gamut)); + + if (nearlyEqual(gamut, SkNamedGamut::kSRGB)) { + if (nearlyEqual(fn, SkNamedTransferFn::kLinear)) { + // Skia doesn't differentiate amongst the RANGES. In Java, we associate + // LINEAR_EXTENDED_SRGB with F16, and LINEAR_SRGB with other Configs. + // Make the same association here. + if (info.colorType() == kRGBA_F16_SkColorType) { + return ADATASPACE_SCRGB_LINEAR; + } + return ADATASPACE_SRGB_LINEAR; + } + + if (nearlyEqual(fn, SkNamedTransferFn::kRec2020)) { + return ADATASPACE_BT709; + } + } + + if (nearlyEqual(fn, SkNamedTransferFn::kSRGB) && nearlyEqual(gamut, SkNamedGamut::kDCIP3)) { + return ADATASPACE_DISPLAY_P3; + } + + if (nearlyEqual(fn, SkNamedTransferFn::k2Dot2) && nearlyEqual(gamut, SkNamedGamut::kAdobeRGB)) { + return ADATASPACE_ADOBE_RGB; + } + + if (nearlyEqual(fn, SkNamedTransferFn::kRec2020) && + nearlyEqual(gamut, SkNamedGamut::kRec2020)) { + return ADATASPACE_BT2020; + } + + if (nearlyEqual(fn, k2Dot6) && nearlyEqual(gamut, kDCIP3)) { + return ADATASPACE_DCI_P3; + } + + return ADATASPACE_UNKNOWN; +} + AndroidBitmapInfo ABitmap_getInfoFromJava(JNIEnv* env, jobject bitmapObj) { uint32_t rowBytes = 0; SkImageInfo imageInfo = GraphicsJNI::getBitmapInfo(env, bitmapObj, &rowBytes); diff --git a/core/jni/android/graphics/apex/include/android/graphics/bitmap.h b/core/jni/android/graphics/apex/include/android/graphics/bitmap.h index f231eeddb7e2..32b8a450e147 100644 --- a/core/jni/android/graphics/apex/include/android/graphics/bitmap.h +++ b/core/jni/android/graphics/apex/include/android/graphics/bitmap.h @@ -17,6 +17,7 @@ #define ANDROID_GRAPHICS_BITMAP_H #include <android/bitmap.h> +#include <android/data_space.h> #include <jni.h> #include <sys/cdefs.h> @@ -49,6 +50,7 @@ void ABitmap_acquireRef(ABitmap* bitmap); void ABitmap_releaseRef(ABitmap* bitmap); AndroidBitmapInfo ABitmap_getInfo(ABitmap* bitmap); +ADataSpace ABitmap_getDataSpace(ABitmap* bitmap); void* ABitmap_getPixels(ABitmap* bitmap); void ABitmap_notifyPixelsChanged(ABitmap* bitmap); @@ -106,6 +108,7 @@ namespace graphics { ABitmap* get() const { return mBitmap; } AndroidBitmapInfo getInfo() const { return ABitmap_getInfo(mBitmap); } + ADataSpace getDataSpace() const { return ABitmap_getDataSpace(mBitmap); } void* getPixels() const { return ABitmap_getPixels(mBitmap); } void notifyPixelsChanged() const { ABitmap_notifyPixelsChanged(mBitmap); } @@ -119,4 +122,4 @@ namespace graphics { }; // namespace android #endif // __cplusplus -#endif // ANDROID_GRAPHICS_BITMAP_H
\ No newline at end of file +#endif // ANDROID_GRAPHICS_BITMAP_H diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 53327bc9d99c..c4ee19519951 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -2627,7 +2627,7 @@ int register_android_media_AudioSystem(JNIEnv *env) gMidAudioRecordRoutingProxy_release = android::GetMethodIDOrDie(env, gClsAudioRecordRoutingProxy, "native_release", "()V"); - AudioSystem::setErrorCallback(android_media_AudioSystem_error_callback); + AudioSystem::addErrorCallback(android_media_AudioSystem_error_callback); RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods)); return RegisterMethodsOrDie(env, kEventHandlerClassPathName, gEventHandlerMethods, diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index 0992bebb5be0..fb8e633fec12 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -537,9 +537,9 @@ public: LOGDEATH("Receiving binderDied() on JavaDeathRecipient %p\n", this); if (mObject != NULL) { JNIEnv* env = javavm_to_jnienv(mVM); - + jobject jBinderProxy = javaObjectForIBinder(env, who.promote()); env->CallStaticVoidMethod(gBinderProxyOffsets.mClass, - gBinderProxyOffsets.mSendDeathNotice, mObject); + gBinderProxyOffsets.mSendDeathNotice, mObject, jBinderProxy); if (env->ExceptionCheck()) { jthrowable excep = env->ExceptionOccurred(); report_exception(env, excep, @@ -1532,8 +1532,9 @@ static int int_register_android_os_BinderProxy(JNIEnv* env) gBinderProxyOffsets.mClass = MakeGlobalRefOrDie(env, clazz); gBinderProxyOffsets.mGetInstance = GetStaticMethodIDOrDie(env, clazz, "getInstance", "(JJ)Landroid/os/BinderProxy;"); - gBinderProxyOffsets.mSendDeathNotice = GetStaticMethodIDOrDie(env, clazz, "sendDeathNotice", - "(Landroid/os/IBinder$DeathRecipient;)V"); + gBinderProxyOffsets.mSendDeathNotice = + GetStaticMethodIDOrDie(env, clazz, "sendDeathNotice", + "(Landroid/os/IBinder$DeathRecipient;Landroid/os/IBinder;)V"); gBinderProxyOffsets.mNativeData = GetFieldIDOrDie(env, clazz, "mNativeData", "J"); clazz = FindClassOrDie(env, "java/lang/Class"); diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp index 59dab0c82162..649e5d2f49fe 100644 --- a/core/jni/android_view_InputEventReceiver.cpp +++ b/core/jni/android_view_InputEventReceiver.cpp @@ -40,10 +40,15 @@ namespace android { static const bool kDebugDispatchCycle = false; +static const char* toString(bool value) { + return value ? "true" : "false"; +} + static struct { jclass clazz; jmethodID dispatchInputEvent; + jmethodID onFocusEvent; jmethodID dispatchBatchedInputEventPending; } gInputEventReceiverClassInfo; @@ -219,8 +224,7 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) { if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Consuming input events, consumeBatches=%s, frameTime=%" PRId64, - getInputChannelName().c_str(), - consumeBatches ? "true" : "false", frameTime); + getInputChannelName().c_str(), toString(consumeBatches), frameTime); } if (consumeBatches) { @@ -235,6 +239,7 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, for (;;) { uint32_t seq; InputEvent* inputEvent; + status_t status = mInputConsumer.consume(&mInputEventFactory, consumeBatches, frameTime, &seq, &inputEvent); if (status) { @@ -302,6 +307,19 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent); break; } + case AINPUT_EVENT_TYPE_FOCUS: { + FocusEvent* focusEvent = static_cast<FocusEvent*>(inputEvent); + if (kDebugDispatchCycle) { + ALOGD("channel '%s' ~ Received focus event: hasFocus=%s, inTouchMode=%s.", + getInputChannelName().c_str(), toString(focusEvent->getHasFocus()), + toString(focusEvent->getInTouchMode())); + } + env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.onFocusEvent, + jboolean(focusEvent->getHasFocus()), + jboolean(focusEvent->getInTouchMode())); + finishInputEvent(seq, true /* handled */); + return OK; + } default: assert(false); // InputConsumer should prevent this from ever happening @@ -421,6 +439,8 @@ int register_android_view_InputEventReceiver(JNIEnv* env) { gInputEventReceiverClassInfo.dispatchInputEvent = GetMethodIDOrDie(env, gInputEventReceiverClassInfo.clazz, "dispatchInputEvent", "(ILandroid/view/InputEvent;)V"); + gInputEventReceiverClassInfo.onFocusEvent = + GetMethodIDOrDie(env, gInputEventReceiverClassInfo.clazz, "onFocusEvent", "(ZZ)V"); gInputEventReceiverClassInfo.dispatchBatchedInputEventPending = GetMethodIDOrDie(env, gInputEventReceiverClassInfo.clazz, "dispatchBatchedInputEventPending", "()V"); diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 4a7276c4f94e..573f378f57b1 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -263,7 +263,8 @@ static jobject nativeScreenshot(JNIEnv* env, jclass clazz, status_t res = ScreenshotClient::capture(displayToken, dataspace, ui::PixelFormat::RGBA_8888, sourceCrop, width, height, - useIdentityTransform, rotation, captureSecureLayers, &buffer, capturedSecureLayers); + useIdentityTransform, ui::toRotation(rotation), + captureSecureLayers, &buffer, capturedSecureLayers); if (res != NO_ERROR) { return NULL; } @@ -422,6 +423,14 @@ static void nativeSetFlags(JNIEnv* env, jclass clazz, jlong transactionObj, transaction->setFlags(ctrl, flags, mask); } +static void nativeSetFrameRateSelectionPriority(JNIEnv* env, jclass clazz, jlong transactionObj, + jlong nativeObject, jint priority) { + auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); + + SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject); + transaction->setFrameRateSelectionPriority(ctrl, priority); +} + static void nativeSetTransparentRegionHint(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject, jobject regionObj) { SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject); @@ -724,7 +733,8 @@ static void nativeSetDisplayProjection(JNIEnv* env, jclass clazz, { auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); - transaction->setDisplayProjection(token, orientation, layerStackRect, displayRect); + transaction->setDisplayProjection(token, static_cast<ui::Rotation>(orientation), + layerStackRect, displayRect); } } @@ -1360,6 +1370,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeSetColorSpaceAgnostic }, {"nativeSetFlags", "(JJII)V", (void*)nativeSetFlags }, + {"nativeSetFrameRateSelectionPriority", "(JJI)V", + (void*)nativeSetFrameRateSelectionPriority }, {"nativeSetWindowCrop", "(JJIIII)V", (void*)nativeSetWindowCrop }, {"nativeSetCornerRadius", "(JJF)V", diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 466544c1448e..05b573ab90b4 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -174,6 +174,8 @@ static const std::string ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY = static constexpr const uint64_t UPPER_HALF_WORD_MASK = 0xFFFF'FFFF'0000'0000; static constexpr const uint64_t LOWER_HALF_WORD_MASK = 0x0000'0000'FFFF'FFFF; +static constexpr const char* kCurProfileDirPath = "/data/misc/profiles/cur"; + /** * The maximum value that the gUSAPPoolSizeMax variable may take. This value * is a mirror of ZygoteServer.USAP_POOL_SIZE_MAX_LIMIT @@ -1156,6 +1158,36 @@ static void isolateAppDataPerPackage(int userId, std::string_view package_name, createAndMountAppData(package_name, ce_data_path, mirrorCePath, actualCePath, fail_fn); } +// Relabel directory +static void relabelDir(const char* path, security_context_t context, fail_fn_t fail_fn) { + if (setfilecon(path, context) != 0) { + fail_fn(CREATE_ERROR("Failed to setfilecon %s %s", path, strerror(errno))); + } +} + +// Relabel all directories under a path non-recursively. +static void relabelAllDirs(const char* path, security_context_t context, fail_fn_t fail_fn) { + DIR* dir = opendir(path); + if (dir == nullptr) { + fail_fn(CREATE_ERROR("Failed to opendir %s", path)); + } + struct dirent* ent; + while ((ent = readdir(dir))) { + if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue; + auto filePath = StringPrintf("%s/%s", path, ent->d_name); + if (ent->d_type == DT_DIR) { + relabelDir(filePath.c_str(), context, fail_fn); + } else if (ent->d_type == DT_LNK) { + if (lsetfilecon(filePath.c_str(), context) != 0) { + fail_fn(CREATE_ERROR("Failed to lsetfilecon %s %s", filePath.c_str(), strerror(errno))); + } + } else { + fail_fn(CREATE_ERROR("Unexpected type: %d %s", ent->d_type, filePath.c_str())); + } + } + closedir(dir); +} + /** * Make other apps data directory not visible in CE, DE storage. * @@ -1215,10 +1247,36 @@ static void isolateAppData(JNIEnv* env, jobjectArray pkg_data_info_list, snprintf(internalDePath, PATH_MAX, "/data/user_de"); snprintf(externalPrivateMountPath, PATH_MAX, "/mnt/expand"); + security_context_t dataDataContext = nullptr; + if (getfilecon(internalDePath, &dataDataContext) < 0) { + fail_fn(CREATE_ERROR("Unable to getfilecon on %s %s", internalDePath, + strerror(errno))); + } + MountAppDataTmpFs(internalLegacyCePath, fail_fn); MountAppDataTmpFs(internalCePath, fail_fn); MountAppDataTmpFs(internalDePath, fail_fn); - MountAppDataTmpFs(externalPrivateMountPath, fail_fn); + + // Mount tmpfs on all external vols DE and CE storage + DIR* dir = opendir(externalPrivateMountPath); + if (dir == nullptr) { + fail_fn(CREATE_ERROR("Failed to opendir %s", externalPrivateMountPath)); + } + struct dirent* ent; + while ((ent = readdir(dir))) { + if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue; + if (ent->d_type != DT_DIR) { + fail_fn(CREATE_ERROR("Unexpected type: %d %s", ent->d_type, ent->d_name)); + } + auto volPath = StringPrintf("%s/%s", externalPrivateMountPath, ent->d_name); + auto cePath = StringPrintf("%s/user", volPath.c_str()); + auto dePath = StringPrintf("%s/user_de", volPath.c_str()); + MountAppDataTmpFs(cePath.c_str(), fail_fn); + MountAppDataTmpFs(dePath.c_str(), fail_fn); + } + closedir(dir); + + bool legacySymlinkCreated = false; for (int i = 0; i < size; i += 3) { jstring package_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i)); @@ -1266,7 +1324,14 @@ static void isolateAppData(JNIEnv* env, jobjectArray pkg_data_info_list, // If it's user 0, create a symlink /data/user/0 -> /data/data, // otherwise create /data/user/$USER if (userId == 0) { - symlink(internalLegacyCePath, internalCeUserPath); + if (!legacySymlinkCreated) { + legacySymlinkCreated = true; + int result = symlink(internalLegacyCePath, internalCeUserPath); + if (result != 0) { + fail_fn(CREATE_ERROR("Failed to create symlink %s %s", internalCeUserPath, + strerror(errno))); + } + } actualCePath = internalLegacyCePath; } else { PrepareDirIfNotPresent(internalCeUserPath, DEFAULT_DATA_DIR_PERMISSION, @@ -1280,6 +1345,86 @@ static void isolateAppData(JNIEnv* env, jobjectArray pkg_data_info_list, isolateAppDataPerPackage(userId, packageName, volUuid, ceDataInode, actualCePath, actualDePath, fail_fn); } + // We set the label AFTER everything is done, as we are applying + // the file operations on tmpfs. If we set the label when we mount + // tmpfs, SELinux will not happy as we are changing system_data_files. + // Relabel dir under /data/user, including /data/user/0 + relabelAllDirs(internalCePath, dataDataContext, fail_fn); + + // Relabel /data/user + relabelDir(internalCePath, dataDataContext, fail_fn); + + // Relabel /data/data + relabelDir(internalLegacyCePath, dataDataContext, fail_fn); + + // Relabel dir under /data/user_de + relabelAllDirs(internalDePath, dataDataContext, fail_fn); + + // Relabel /data/user_de + relabelDir(internalDePath, dataDataContext, fail_fn); + + // Relabel CE and DE dirs under /mnt/expand + dir = opendir(externalPrivateMountPath); + if (dir == nullptr) { + fail_fn(CREATE_ERROR("Failed to opendir %s", externalPrivateMountPath)); + } + while ((ent = readdir(dir))) { + if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue; + auto volPath = StringPrintf("%s/%s", externalPrivateMountPath, ent->d_name); + auto cePath = StringPrintf("%s/user", volPath.c_str()); + auto dePath = StringPrintf("%s/user_de", volPath.c_str()); + + relabelAllDirs(cePath.c_str(), dataDataContext, fail_fn); + relabelDir(cePath.c_str(), dataDataContext, fail_fn); + relabelAllDirs(dePath.c_str(), dataDataContext, fail_fn); + relabelDir(dePath.c_str(), dataDataContext, fail_fn); + } + closedir(dir); + + freecon(dataDataContext); +} + +/** + * Like isolateAppData(), isolate jit profile directories, so apps don't see what + * other apps are installed by reading content inside /data/misc/profiles/cur. + * + * The implementation is similar to isolateAppData(), it creates a tmpfs + * on /data/misc/profiles/cur, and bind mounts related package profiles to it. + */ +static void isolateJitProfile(JNIEnv* env, jobjectArray pkg_data_info_list, + uid_t uid, const char* process_name, jstring managed_nice_name, + fail_fn_t fail_fn) { + + auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1); + const userid_t user_id = multiuser_get_user_id(uid); + + int size = (pkg_data_info_list != nullptr) ? env->GetArrayLength(pkg_data_info_list) : 0; + // Size should be a multiple of 3, as it contains list of <package_name, volume_uuid, inode> + if ((size % 3) != 0) { + fail_fn(CREATE_ERROR("Wrong pkg_inode_list size %d", size)); + } + + // Mount (namespace) tmpfs on profile directory, so apps no longer access + // the original profile directory anymore. + MountAppDataTmpFs(kCurProfileDirPath, fail_fn); + + // Create profile directory for this user. + std::string actualCurUserProfile = StringPrintf("%s/%d", kCurProfileDirPath, user_id); + PrepareDir(actualCurUserProfile.c_str(), DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, + fail_fn); + + for (int i = 0; i < size; i += 3) { + jstring package_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i)); + std::string packageName = extract_fn(package_str).value(); + + std::string actualCurPackageProfile = StringPrintf("%s/%s", actualCurUserProfile.c_str(), + packageName.c_str()); + std::string mirrorCurPackageProfile = StringPrintf("/data_mirror/cur_profiles/%d/%s", + user_id, packageName.c_str()); + + PrepareDir(actualCurPackageProfile, DEFAULT_DATA_DIR_PERMISSION, uid, uid, fail_fn); + BindMount(mirrorCurPackageProfile, actualCurPackageProfile, fail_fn); + } } // Utility routine to specialize a zygote child process. @@ -1331,9 +1476,9 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, // Isolated process / webview / app zygote should be gated by SELinux and file permission // so they can't even traverse CE / DE directories. if (pkg_data_info_list != nullptr - && GetBoolProperty(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, false)) { - isolateAppData(env, pkg_data_info_list, uid, process_name, managed_nice_name, - fail_fn); + && GetBoolProperty(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, true)) { + isolateAppData(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn); + isolateJitProfile(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn); } // If this zygote isn't root, it won't be able to create a process group, diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp index 082a2892e34b..1fcc8acb5879 100644 --- a/core/jni/fd_utils.cpp +++ b/core/jni/fd_utils.cpp @@ -39,6 +39,7 @@ static const char* kPathWhitelist[] = { "/apex/com.android.media/javalib/updatable-media.jar", "/apex/com.android.mediaprovider/javalib/framework-mediaprovider.jar", "/apex/com.android.os.statsd/javalib/framework-statsd.jar", + "/apex/com.android.permission/javalib/framework-permission.jar", "/apex/com.android.sdkext/javalib/framework-sdkextensions.jar", "/apex/com.android.wifi/javalib/framework-wifi.jar", "/apex/com.android.tethering/javalib/framework-tethering.jar", diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto index 8f9c041b4ad2..da8c944ba278 100644 --- a/core/proto/android/os/incident.proto +++ b/core/proto/android/os/incident.proto @@ -165,6 +165,11 @@ message IncidentProto { (section).args = "security -L" ]; + optional android.util.PersistedLogProto persisted_logs = 1116 [ + (section).type = SECTION_COMMAND, + (section).args = "/system/bin/sh /system/bin/incident-helper-cmd -l run persisted_logs --limit 10MB" + ]; + // Stack dumps optional android.os.BackTraceProto native_traces = 1200 [ (section).type = SECTION_TOMBSTONE, diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto index 06040a599df1..b71e5395730e 100644 --- a/core/proto/android/server/jobscheduler.proto +++ b/core/proto/android/server/jobscheduler.proto @@ -32,8 +32,8 @@ import "frameworks/base/core/proto/android/server/appstatetracker.proto"; import "frameworks/base/core/proto/android/server/job/enums.proto"; import "frameworks/base/core/proto/android/server/statlogger.proto"; import "frameworks/base/core/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/util/quotatracker.proto"; -// Next tag: 21 message JobSchedulerServiceDumpProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; @@ -160,10 +160,13 @@ message JobSchedulerServiceDumpProto { optional JobConcurrencyManagerProto concurrency_manager = 20; optional JobStorePersistStatsProto persist_stats = 21; + + optional .android.util.quota.CountQuotaTrackerProto quota_tracker = 22; + + // Next tag: 23 } // A com.android.server.job.JobSchedulerService.Constants object. -// Next tag: 29 message ConstantsProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; @@ -246,6 +249,14 @@ message ConstantsProto { // Whether to use heartbeats or rolling window for quota management. True // will use heartbeats, false will use a rolling window. reserved 23; // use_heartbeats + // Whether to enable quota limits on APIs. + optional bool enable_api_quotas = 31; + // The maximum number of schedule() calls an app can make in a set amount of time. + optional int32 api_quota_schedule_count = 32; + // The time window that {@link #API_QUOTA_SCHEDULE_COUNT} should be evaluated over. + optional int64 api_quota_schedule_window_ms = 33; + // Whether or not to throw an exception when an app hits its schedule quota limit. + optional bool api_quota_schedule_throw_exception = 34; message QuotaController { option (.android.msg_privacy).dest = DEST_AUTOMATIC; @@ -331,7 +342,7 @@ message ConstantsProto { // In this time after screen turns on, we increase job concurrency. optional int32 screen_off_job_concurrency_increase_delay_ms = 28; - // Next tag: 31 + // Next tag: 35 } // Next tag: 4 @@ -651,8 +662,7 @@ message StateControllerProto { optional bool is_active = 2; // The time this timer last became active. Only valid if is_active is true. optional int64 start_time_elapsed = 3; - // How many background jobs are currently running. Valid only if the device is_active - // is true. + // How many background jobs are currently running. Valid only if is_active is true. optional int32 bg_job_count = 4; // All of the jobs that the Timer is currently tracking. repeated JobStatusShortInfoProto running_jobs = 5; diff --git a/core/proto/android/stats/sysui/notification_enums.proto b/core/proto/android/stats/sysui/notification_enums.proto new file mode 100644 index 000000000000..09837022e50d --- /dev/null +++ b/core/proto/android/stats/sysui/notification_enums.proto @@ -0,0 +1,29 @@ +/* + * 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. + */ + +syntax = "proto2"; + +package android.stats.sysui; + +// Enum used in NotificationReported and NotificationChannelModified atoms +enum NotificationImportance { // Constants from NotificationManager.java + IMPORTANCE_UNSPECIFIED = -1000; // Should not occur for real notifications. + IMPORTANCE_NONE = 0; // No importance: does not show in the shade. + IMPORTANCE_MIN = 1; // Minimum to show in the shade. + IMPORTANCE_LOW = 2; // Shows in shade, maybe status bar, no buzz/beep. + IMPORTANCE_DEFAULT = 3; // Shows everywhere, makes noise, no heads-up. + IMPORTANCE_HIGH = 4; // Shows everywhere, makes noise, heads-up, may full-screen. +} diff --git a/core/proto/android/util/log.proto b/core/proto/android/util/log.proto index 09870ae55cfe..a214a1acf1bd 100644 --- a/core/proto/android/util/log.proto +++ b/core/proto/android/util/log.proto @@ -94,3 +94,16 @@ message LogProto { repeated BinaryLogEntry binary_logs = 2; } +message PersistedLogProto { + option (android.msg_privacy).dest = DEST_EXPLICIT; + + repeated TextLogEntry main_logs = 1; + repeated TextLogEntry radio_logs = 2; + repeated TextLogEntry events_logs = 3; + repeated TextLogEntry system_logs = 4; + repeated TextLogEntry crash_logs = 5; + repeated TextLogEntry stats_logs = 6; + repeated TextLogEntry security_logs = 7; + repeated TextLogEntry kernel_logs = 8; +} + diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index d4768c08f89f..f8c51666d19a 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -362,7 +362,6 @@ <protected-broadcast android:name="android.net.wifi.WIFI_STATE_CHANGED" /> <protected-broadcast android:name="android.net.wifi.WIFI_AP_STATE_CHANGED" /> <protected-broadcast android:name="android.net.wifi.WIFI_CREDENTIAL_CHANGED" /> - <protected-broadcast android:name="android.net.wifi.WIFI_SCAN_AVAILABLE" /> <protected-broadcast android:name="android.net.wifi.aware.action.WIFI_AWARE_STATE_CHANGED" /> <protected-broadcast android:name="android.net.wifi.rtt.action.WIFI_RTT_STATE_CHANGED" /> <protected-broadcast android:name="android.net.wifi.SCAN_RESULTS" /> @@ -377,6 +376,7 @@ <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_SUBSCRIPTION_REMEDIATION" /> <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_LAUNCH_OSU_VIEW" /> <protected-broadcast android:name="android.net.wifi.action.WIFI_NETWORK_SUGGESTION_POST_CONNECTION" /> + <protected-broadcast android:name="android.net.wifi.action.WIFI_SCAN_AVAILABLE" /> <protected-broadcast android:name="android.net.wifi.supplicant.CONNECTION_CHANGE" /> <protected-broadcast android:name="android.net.wifi.supplicant.STATE_CHANGE" /> <protected-broadcast android:name="android.net.wifi.p2p.STATE_CHANGED" /> @@ -2568,7 +2568,7 @@ <!-- Allows telephony to suggest the time / time zone. <p>Not for use by third-party applications. - @hide + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @hide --> <permission android:name="android.permission.SUGGEST_PHONE_TIME_AND_ZONE" android:protectionLevel="signature|telephony" /> @@ -3386,6 +3386,14 @@ <permission android:name="android.permission.NOTIFY_TV_INPUTS" android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows an application to interact with tuner resources through + Tuner Resource Manager. + <p>Protection level: signature|privileged + <p>Not for use by third-party applications. + @hide --> + <permission android:name="android.permission.TUNER_RESOURCE_ACCESS" + android:protectionLevel="signature|privileged" /> + <!-- Must be required by a {@link android.media.routing.MediaRouteService} to ensure that only the system can interact with it. @hide --> @@ -3972,6 +3980,13 @@ <permission android:name="android.permission.BACKUP" android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows an application to make modifications to device settings such that these + modifications will be overridden by settings restore.. + <p>Not for use by third-party applications. + @hide --> + <permission android:name="android.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE" + android:protectionLevel="signature|setup" /> + <!-- @SystemApi Allows application to manage {@link android.security.keystore.recovery.RecoveryController}. <p>Not for use by third-party applications. @@ -4968,6 +4983,12 @@ android:process=":ui"> </activity> + <activity android:name="com.android.internal.app.BlockedAppActivity" + android:theme="@style/Theme.Dialog.Confirmation" + android:excludeFromRecents="true" + android:process=":ui"> + </activity> + <activity android:name="com.android.settings.notification.NotificationAccessConfirmationActivity" android:theme="@style/Theme.Dialog.Confirmation" android:excludeFromRecents="true"> diff --git a/core/res/res/drawable/ic_accessibility_color_correction.xml b/core/res/res/drawable/ic_accessibility_color_correction.xml new file mode 100644 index 000000000000..02fa4b807155 --- /dev/null +++ b/core/res/res/drawable/ic_accessibility_color_correction.xml @@ -0,0 +1,37 @@ +<vector android:height="24dp" android:viewportHeight="192" + android:viewportWidth="192" android:width="24dp" + xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#00BCD4" android:pathData="M37.14,173.74L8.61,83.77c-1.72,-5.75 0.26,-11.97 4.97,-15.63L87.15,11c5.2,-4.04 12.46,-3.99 17.61,0.12l73.81,58.94c4.63,3.7 6.53,9.88 4.8,15.57l-28.48,88.18c-1.85,6.06 -7.39,10.19 -13.67,10.19H50.84C44.53,184 38.97,179.83 37.14,173.74z"/> + <path android:fillAlpha="0.2" android:fillColor="#FFFFFF" + android:pathData="M13.58,69.14L87.15,12c5.2,-4.04 12.46,-3.99 17.61,0.12l73.81,58.94c3.36,2.68 5.27,6.67 5.41,10.82c0.15,-4.51 -1.79,-8.93 -5.41,-11.82l-73.81,-58.94C99.61,7.01 92.35,6.96 87.15,11L13.58,68.14c-3.71,2.88 -5.72,7.36 -5.56,11.94C8.17,75.85 10.14,71.81 13.58,69.14z" android:strokeAlpha="0.2"/> + <path android:fillAlpha="0.2" android:fillColor="#263238" + android:pathData="M183.35,84.63l-28.48,88.18c-1.85,6.06 -7.39,10.19 -13.67,10.19H50.84c-6.31,0 -11.87,-4.17 -13.7,-10.26L8.61,82.77c-0.36,-1.22 -0.55,-2.46 -0.59,-3.69c-0.06,1.56 0.13,3.14 0.59,4.69l28.53,89.97c1.83,6.09 7.39,10.26 13.7,10.26h90.37c6.28,0 11.82,-4.13 13.67,-10.19l28.48,-88.18c0.48,-1.57 0.67,-3.17 0.61,-4.75C183.92,82.13 183.73,83.39 183.35,84.63z" android:strokeAlpha="0.2"/> + <path android:pathData="M60.01,135L60.01,135H60l48.04,49h33.17c6.28,0 11.82,-4.13 13.67,-10.19l18.68,-57.84L129.51,72.2l-0.01,0c0.27,0.27 0.52,0.5 0.77,0.77l0.55,0.55c1.57,1.56 1.57,4.08 -0.04,5.68l-12.56,12.49l6.36,6.3l1.38,1.37l-5.67,5.64l-5.71,-5.68L79.15,135H60.42H60.01z"> + <aapt:attr name="android:fillColor"> + <gradient android:endX="155.9627" android:endY="165.1493" + android:startX="98.4649" android:startY="107.6516" android:type="linear"> + <item android:color="#19263238" android:offset="0"/> + <item android:color="#00212121" android:offset="1"/> + </gradient> + </aapt:attr> + </path> + <path android:fillColor="#0097A7" android:pathData="M68.55,120.35l32.173,-32.173l7.658,7.658l-32.173,32.173z"/> + <path android:fillColor="#FFFFFF" android:pathData="M130.83,73.52l-9.42,-9.36c-1.57,-1.56 -4.1,-1.56 -5.67,0l-12.56,12.48L95.42,69l-5.67,5.64l5.71,5.68L60,116.97V135h19.15l35.42,-35.68l5.71,5.68l5.67,-5.64l-7.73,-7.68l12.56,-12.48C132.4,77.6 132.4,75.08 130.83,73.52zM74.98,126.77l-6.43,-6.43l32.17,-32.17l6.43,6.43L74.98,126.77z"/> + <path android:fillAlpha="0.1" android:fillColor="#263238" + android:pathData="M120.28,105l-5.71,-5.68l-35.42,35.68l-19.15,0l1,1l19.15,0l35.42,-35.68l5.71,5.68l5.68,-5.64l-1,-1z" android:strokeAlpha="0.1"/> + <path android:fillAlpha="0.1" android:fillColor="#263238" + android:pathData="M90.75,75.64l-0.01,0l4.71,4.68l0.01,0z" android:strokeAlpha="0.1"/> + <path android:fillAlpha="0.1" android:fillColor="#263238" + android:pathData="M131.83,74.52l-0.97,-0.97c1.54,1.56 1.53,4.06 -0.07,5.65l-12.56,12.48l1,1l12.55,-12.48C133.4,78.6 133.4,76.08 131.83,74.52z" android:strokeAlpha="0.1"/> + <path android:fillAlpha="0.1" android:fillColor="#263238" + android:pathData="M101.72,89.17l6.67,6.66l0,0l-7.67,-7.66l-32.17,32.17l1,1z" android:strokeAlpha="0.1"/> + <path android:pathData="M37.13,173.7L8.62,83.69c-1.7,-5.8 0.3,-12 5,-15.6l73.52,-57.1c5.2,-4 12.5,-4 17.6,0.1l73.82,58.9c4.6,3.7 6.5,9.9 4.8,15.6l-28.51,88.21c-1.8,6.1 -7.4,10.2 -13.7,10.2H50.83C44.53,184 38.93,179.8 37.13,173.7z"> + <aapt:attr name="android:fillColor"> + <gradient android:centerX="21.977" android:centerY="23.8809" + android:gradientRadius="158.0384" android:type="radial"> + <item android:color="#19FFFFFF" android:offset="0"/> + <item android:color="#00FFFFFF" android:offset="1"/> + </gradient> + </aapt:attr> + </path> +</vector> diff --git a/core/res/res/drawable/ic_accessibility_color_inversion.xml b/core/res/res/drawable/ic_accessibility_color_inversion.xml new file mode 100644 index 000000000000..97b30b0df4d5 --- /dev/null +++ b/core/res/res/drawable/ic_accessibility_color_inversion.xml @@ -0,0 +1,47 @@ +<vector android:height="24dp" android:viewportHeight="192" + android:viewportWidth="192" android:width="24dp" + xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#546E7A" android:pathData="M37.14,173.74L8.61,83.77c-1.72,-5.75 0.26,-11.97 4.97,-15.63L87.15,11c5.2,-4.04 12.46,-3.99 17.61,0.12l73.81,58.94c4.63,3.7 6.53,9.88 4.8,15.57l-28.48,88.18c-1.85,6.06 -7.39,10.19 -13.67,10.19H50.84C44.53,184 38.97,179.83 37.14,173.74z"/> + <path android:fillAlpha="0.2" android:fillColor="#263238" + android:pathData="M183.37,84.63l-28.48,88.18c-1.85,6.06 -7.39,10.19 -13.67,10.19H50.84c-6.31,0 -11.87,-4.17 -13.7,-10.26L8.61,82.77c-0.36,-1.22 -0.55,-2.46 -0.59,-3.69c-0.06,1.56 0.13,3.14 0.59,4.69l28.53,89.97c1.83,6.09 7.39,10.26 13.7,10.26h90.38c6.28,0 11.82,-4.13 13.67,-10.19l28.48,-88.18c0.48,-1.57 0.67,-3.17 0.61,-4.75C183.94,82.13 183.74,83.39 183.37,84.63z" android:strokeAlpha="0.2"/> + <path android:fillAlpha="0.2" android:fillColor="#FFFFFF" + android:pathData="M13.58,69.14L87.15,12c5.2,-4.04 12.46,-3.99 17.61,0.12l73.81,58.94c3.36,2.68 5.27,6.67 5.41,10.82c0.15,-4.51 -1.79,-8.93 -5.41,-11.82l-73.81,-58.94C99.61,7.01 92.35,6.96 87.15,11L13.58,68.14c-3.71,2.88 -5.72,7.36 -5.56,11.94C8.17,75.85 10.14,71.81 13.58,69.14z" android:strokeAlpha="0.2"/> + <path android:fillAlpha="0.2" android:fillColor="#FFFFFF" + android:pathData="M53,130.05l5.03,-4.79L44.16,112c-0.05,0.78 -0.14,1.52 -0.15,2.34C43.62,136.61 61.27,154.56 84,156v-5.31C70.13,149.64 58.56,141.54 53,130.05z" android:strokeAlpha="0.2"/> + <path android:fillAlpha="0.2" android:fillColor="#FFFFFF" + android:pathData="M109,52v5.31c13.65,1.05 24.67,9.15 30.11,20.64l-4.92,4.79L147.81,96c0.09,-0.78 0.17,-1.53 0.19,-2.34C148.38,71.39 131.36,53.44 109,52z" android:strokeAlpha="0.2"/> + <path android:pathData="M154.89,173.81l13.57,-42.02l-46.53,-46.56C125.75,90.5 128,96.98 128,104c0,17.7 -14.3,32 -32,32c-8.64,0 -16.47,-3.42 -22.22,-8.97l0.9,0.91L130.73,184h10.49C147.5,184 153.04,179.87 154.89,173.81z"> + <aapt:attr name="android:fillColor"> + <gradient android:endX="153.3523" android:endY="161.6371" + android:startX="90.6075" android:startY="98.8923" android:type="linear"> + <item android:color="#33263238" android:offset="0"/> + <item android:color="#05263238" android:offset="1"/> + </gradient> + </aapt:attr> + </path> + <path android:pathData="M96,129.6V78.4c-14.11,0 -25.6,11.49 -25.6,25.6S81.89,129.6 96,129.6z"> + <aapt:attr name="android:fillColor"> + <gradient android:endX="150.8492" android:endY="164.1401" + android:startX="88.1044" android:startY="101.3954" android:type="linear"> + <item android:color="#33263238" android:offset="0"/> + <item android:color="#05263238" android:offset="1"/> + </gradient> + </aapt:attr> + </path> + <path android:fillAlpha="0.2" android:fillColor="#263238" + android:pathData="M96,136c-17.53,0 -31.72,-14.04 -31.99,-31.5c0,0.17 -0.01,0.33 -0.01,0.5c0,17.7 14.3,32 32,32s32,-14.3 32,-32c0,-0.17 -0.01,-0.33 -0.01,-0.5C127.72,121.96 113.53,136 96,136z" android:strokeAlpha="0.2"/> + <path android:fillAlpha="0.2" android:fillColor="#263238" + android:pathData="M70.4,104c0,0.17 0.01,0.33 0.01,0.5C70.68,90.62 82.06,79.4 96,79.4v-1C81.89,78.4 70.4,89.88 70.4,104z" android:strokeAlpha="0.2"/> + <path android:fillAlpha="0.2" android:fillColor="#FFFFFF" + android:pathData="M96,72c-17.7,0 -32,14.3 -32,32s14.3,32 32,32s32,-14.3 32,-32S113.7,72 96,72zM70.4,104c0,-14.11 11.49,-25.6 25.6,-25.6v51.2C81.89,129.6 70.4,118.11 70.4,104z" android:strokeAlpha="0.2"/> + <path android:fillColor="#FFFFFF" android:pathData="M96,72c-17.7,0 -32,14.3 -32,32s14.3,32 32,32s32,-14.3 32,-32S113.7,72 96,72zM70.4,104c0,-14.11 11.49,-25.6 25.6,-25.6v51.2C81.89,129.6 70.4,118.11 70.4,104z"/> + <path android:pathData="M37.14,173.74L8.61,83.77c-1.72,-5.75 0.26,-11.97 4.97,-15.63L87.15,11c5.2,-4.04 12.46,-3.99 17.61,0.12l73.81,58.94c4.63,3.7 6.53,9.88 4.8,15.57l-28.48,88.18c-1.85,6.06 -7.39,10.19 -13.67,10.19H50.84C44.53,184 38.97,179.83 37.14,173.74z"> + <aapt:attr name="android:fillColor"> + <gradient android:endX="156.2451" android:endY="171.4516" + android:startX="37.0633" android:startY="52.269802" android:type="linear"> + <item android:color="#19FFFFFF" android:offset="0"/> + <item android:color="#00FFFFFF" android:offset="1"/> + </gradient> + </aapt:attr> + </path> +</vector> diff --git a/core/res/res/layout/autofill_inline_suggestion.xml b/core/res/res/layout/autofill_inline_suggestion.xml new file mode 100644 index 000000000000..f7ac1642f6b7 --- /dev/null +++ b/core/res/res/layout/autofill_inline_suggestion.xml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="wrap_content" + android:layout_height="56dp" + android:background="@color/white" + android:orientation="horizontal" + android:paddingStart="12dp" + android:paddingEnd="12dp"> + + <ImageView + android:id="@+id/autofill_inline_suggestion_start_icon" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_gravity="center" + android:contentDescription="autofill_inline_suggestion_start_icon" /> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_gravity="center" + android:layout_weight="1" + android:layout_marginStart="12dp" + android:layout_marginEnd="12dp" + android:orientation="vertical" + android:gravity="center_vertical"> + + <TextView + android:id="@+id/autofill_inline_suggestion_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ellipsize="end" + android:maxLines="1" + tools:text="username1"/> + + <TextView + android:id="@+id/autofill_inline_suggestion_subtitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ellipsize="end" + android:maxLines="1" + tools:text="inline fill service"/> + </LinearLayout> + + <ImageView + android:id="@+id/autofill_inline_suggestion_end_icon" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_gravity="center" + android:contentDescription="autofill_inline_suggestion_end_icon" /> +</LinearLayout> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 9c08728b559e..44754157c5b5 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3752,6 +3752,8 @@ </p> --> <attr name="canRequestFingerprintGestures" format="boolean" /> + <!-- Attribute whether the accessibility service wants to be able to take screenshot. --> + <attr name="canTakeScreenshot" format="boolean" /> <!-- Animated image of the accessibility service purpose or behavior, to help users understand how the service can help them.--> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 6435cddebd1e..94f3b8ae6714 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -2005,6 +2005,15 @@ <attr name="maxSdkVersion" /> </declare-styleable> + <!-- The <code>extension-sdk</code> tag is a child of the <uses-sdk> tag, + and specifies required extension sdk features. --> + <declare-styleable name="AndroidManifestExtensionSdk"> + <!-- The extension SDK version that this tag refers to. --> + <attr name="sdkVersion" format="integer" /> + <!-- The minimum version of the extension SDK this application requires.--> + <attr name="minExtensionVersion" format="integer" /> + </declare-styleable> + <!-- The <code>library</code> tag declares that this apk is providing itself as a shared library for other applications to use. It can only be used with apks that are built in to the system image. Other apks can link to diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 6f554f0264df..a78195b05d8f 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4294,4 +4294,7 @@ <!-- Class name of the custom country detector to be used. --> <string name="config_customCountryDetector" translatable="false">com.android.server.location.ComprehensiveCountryDetector</string> + + <!-- Package name of the required service extension package. --> + <string name="config_servicesExtensionPackage" translatable="false">android.ext.services</string> </resources> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index bf7558c8597a..d437aa1c340c 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -777,4 +777,9 @@ <!-- Assistant handles --> <dimen name="assist_handle_shadow_radius">2dp</dimen> + <!-- For Waterfall Display --> + <dimen name="waterfall_display_left_edge_size">0px</dimen> + <dimen name="waterfall_display_top_edge_size">0px</dimen> + <dimen name="waterfall_display_right_edge_size">0px</dimen> + <dimen name="waterfall_display_bottom_edge_size">0px</dimen> </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 6cf6a6828237..36dbcbd53977 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3007,6 +3007,11 @@ <public name="featureId" /> <public name="supportsInlineSuggestions" /> <public name="crossProfile" /> + <public name="canTakeScreenshot"/> + <!-- @hide @SystemApi --> + <public name="sdkVersion" /> + <!-- @hide @SystemApi --> + <public name="minExtensionVersion" /> </public-group> <public-group type="drawable" first-id="0x010800b5"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index a0e40646ead9..a81565a81670 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -723,11 +723,11 @@ <string name="permgroupdesc_sms">send and view SMS messages</string> <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permgrouplab_storage">Storage</string> + <string name="permgrouplab_storage">Files and media</string> <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. --> <string name="permgroupdesc_storage">access photos, media, and files on your device</string> - <!-- Title of a category of application permissioncds, listed so the user can choose whether they want to allow the application to do this. --> + <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. --> <string name="permgrouplab_microphone">Microphone</string> <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. --> <string name="permgroupdesc_microphone">record audio</string> @@ -794,6 +794,11 @@ <string name="capability_desc_canCaptureFingerprintGestures">Can capture gestures performed on the device\'s fingerprint sensor.</string> + <!-- Title for the capability of an accessibility service to take screenshot. [CHAR LIMIT=32] --> + <string name="capability_title_canTakeScreenshot">Take screenshot</string> + <!-- Description for the capability of an accessibility service to take screenshot. [CHAR LIMIT=NONE] --> + <string name="capability_desc_canTakeScreenshot">Can take a screenshot of the display.</string> + <!-- Permissions --> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> @@ -4922,6 +4927,13 @@ <!-- Title for button to turn on work profile. [CHAR LIMIT=NONE] --> <string name="work_mode_turn_on">Turn on</string> + <!-- Title of the dialog that is shown when the user tries to launch a suspended application [CHAR LIMIT=50] --> + <string name="app_blocked_title">App is not available</string> + <!-- Default message shown in the dialog that is shown when the user tries to launch a suspended application [CHAR LIMIT=NONE] --> + <string name="app_blocked_message"> + <xliff:g id="app_name" example="Gmail">%1$s</xliff:g> is not available right now. + </string> + <!-- Message displayed in dialog when app is too old to run on this verison of android. [CHAR LIMIT=NONE] --> <string name="deprecated_target_sdk_message">This app was built for an older version of Android and may not work properly. Try checking for updates, or contact the developer.</string> <!-- Title for button to see application detail in app store which it came from - it may allow user to update to newer version. [CHAR LIMIT=50] --> @@ -5207,25 +5219,14 @@ <!-- Battery saver strings --> <!-- The user visible name of the notification channel for battery saver notifications [CHAR_LIMIT=80] --> <string name="battery_saver_notification_channel_name">Battery Saver</string> - <!-- Title of notification letting users know why battery saver didn't turn back on automatically after the device was unplugged [CHAR_LIMIT=NONE] --> - <string name="battery_saver_sticky_disabled_notification_title">Battery Saver won\u2019t reactivate until battery low again</string> - <!-- Summary of notification letting users know why battery saver didn't turn back on automatically after the device was unplugged [CHAR_LIMIT=NONE] --> - <string name="battery_saver_sticky_disabled_notification_summary">Battery has been charged to a sufficient level. Battery Saver won\u2019t reactivate until the battery is low again.</string> + <!-- Title of notification letting users know that battery saver is now off [CHAR_LIMIT=80] --> + <string name="battery_saver_off_notification_title">Battery Saver turned off</string> <!-- Title of notification letting users know the battery level at the time the notification was posted [CHAR_LIMIT=80] --> - <string name="battery_saver_charged_notification_title" product="default">Phone <xliff:g id="charge level" example="90%">%1$s</xliff:g> charged</string> + <string name="battery_saver_charged_notification_summary" product="default">Phone has enough charge. Features no longer restricted.</string> <!-- Title of notification letting users know the battery level at the time the notification was posted [CHAR_LIMIT=80] --> - <string name="battery_saver_charged_notification_title" product="tablet">Tablet <xliff:g id="charge level" example="90%">%1$s</xliff:g> charged</string> + <string name="battery_saver_charged_notification_summary" product="tablet">Tablet has enough charge. Features no longer restricted.</string> <!-- Title of notification letting users know the battery level at the time the notification was posted [CHAR_LIMIT=80] --> - <string name="battery_saver_charged_notification_title" product="device">Device <xliff:g id="charge level" example="90%">%1$s</xliff:g> charged</string> - <!-- Summary of notification letting users know that battery saver is now off [CHAR_LIMIT=NONE] --> - <string name="battery_saver_off_notification_summary">Battery Saver is off. Features no longer restricted.</string> - <!-- Alternative summary of notification letting users know that battery saver has been turned off. - If it's easy to translate the difference between "Battery Saver turned off. Features no longer restricted." - and "Battery Saver is off. Features no longer restricted." into the target language, - then translate "Battery Saver turned off. Features no longer restricted." - If the translation doesn't make a difference or the difference is hard to capture in the target language, - then translate "Battery Saver is off. Features no longer restricted." instead. [CHAR_LIMIT=NONE] --> - <string name="battery_saver_off_alternative_notification_summary">Battery Saver turned off. Features no longer restricted.</string> + <string name="battery_saver_charged_notification_summary" product="device">Device has enough charge. Features no longer restricted.</string> <!-- Description of media type: folder or directory that contains additional files. [CHAR LIMIT=32] --> <string name="mime_type_folder">Folder</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 9e117498eb10..669b41e53ba1 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3036,6 +3036,9 @@ <java-symbol type="string" name="app_suspended_more_details" /> <java-symbol type="string" name="app_suspended_default_message" /> + <java-symbol type="string" name="app_blocked_title" /> + <java-symbol type="string" name="app_blocked_message" /> + <!-- Used internally for assistant to launch activity transitions --> <java-symbol type="id" name="cross_task_transition" /> @@ -3214,6 +3217,8 @@ <java-symbol type="string" name="edit_accessibility_shortcut_menu_button" /> <java-symbol type="string" name="cancel_accessibility_shortcut_menu_button" /> + <java-symbol type="drawable" name="ic_accessibility_color_inversion" /> + <java-symbol type="drawable" name="ic_accessibility_color_correction" /> <java-symbol type="drawable" name="ic_accessibility_magnification" /> <java-symbol type="drawable" name="ic_delete_item" /> @@ -3230,6 +3235,7 @@ <java-symbol type="layout" name="autofill_dataset_picker"/> <java-symbol type="layout" name="autofill_dataset_picker_fullscreen"/> <java-symbol type="layout" name="autofill_dataset_picker_header_footer"/> + <java-symbol type="layout" name="autofill_inline_suggestion" /> <java-symbol type="id" name="autofill" /> <java-symbol type="id" name="autofill_dataset_footer"/> <java-symbol type="id" name="autofill_dataset_header"/> @@ -3237,6 +3243,10 @@ <java-symbol type="id" name="autofill_dataset_list"/> <java-symbol type="id" name="autofill_dataset_picker"/> <java-symbol type="id" name="autofill_dataset_title" /> + <java-symbol type="id" name="autofill_inline_suggestion_end_icon" /> + <java-symbol type="id" name="autofill_inline_suggestion_start_icon" /> + <java-symbol type="id" name="autofill_inline_suggestion_subtitle" /> + <java-symbol type="id" name="autofill_inline_suggestion_title" /> <java-symbol type="id" name="autofill_save_custom_subtitle" /> <java-symbol type="id" name="autofill_save_icon" /> <java-symbol type="id" name="autofill_save_no" /> @@ -3644,11 +3654,8 @@ <java-symbol type="bool" name="config_useSystemProvidedLauncherForSecondary" /> <java-symbol type="string" name="battery_saver_notification_channel_name" /> - <java-symbol type="string" name="battery_saver_sticky_disabled_notification_title" /> - <java-symbol type="string" name="battery_saver_sticky_disabled_notification_summary" /> - <java-symbol type="string" name="battery_saver_charged_notification_title" /> - <java-symbol type="string" name="battery_saver_off_notification_summary" /> - <java-symbol type="string" name="battery_saver_off_alternative_notification_summary" /> + <java-symbol type="string" name="battery_saver_off_notification_title" /> + <java-symbol type="string" name="battery_saver_charged_notification_summary" /> <java-symbol type="string" name="dynamic_mode_notification_channel_name" /> <java-symbol type="string" name="dynamic_mode_notification_title" /> <java-symbol type="string" name="dynamic_mode_notification_summary" /> @@ -3801,5 +3808,15 @@ <!-- Assistant handles --> <java-symbol type="dimen" name="assist_handle_shadow_radius" /> + <!-- For Waterfall Display --> + <java-symbol type="dimen" name="waterfall_display_left_edge_size" /> + <java-symbol type="dimen" name="waterfall_display_top_edge_size" /> + <java-symbol type="dimen" name="waterfall_display_right_edge_size" /> + <java-symbol type="dimen" name="waterfall_display_bottom_edge_size" /> + + <!-- Accessibility take screenshot --> + <java-symbol type="string" name="capability_desc_canTakeScreenshot" /> + <java-symbol type="string" name="capability_title_canTakeScreenshot" /> + <java-symbol type="string" name="config_servicesExtensionPackage" /> </resources> diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index caae9088e9b5..3836d6f4115d 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -8,6 +8,7 @@ android_test { "EnabledTestApp/src/**/*.java", "BinderProxyCountingTestApp/src/**/*.java", "BinderProxyCountingTestService/src/**/*.java", + "BinderDeathRecipientHelperApp/src/**/*.java", "aidl/**/I*.aidl", ], @@ -59,7 +60,11 @@ android_test { resource_dirs: ["res"], resource_zips: [":FrameworksCoreTests_apks_as_resources"], - data: [":BstatsTestApp"], + data: [ + ":BstatsTestApp", + ":BinderDeathRecipientHelperApp1", + ":BinderDeathRecipientHelperApp2", + ], } // Rules to copy all the test apks to the intermediate raw resource directory @@ -79,6 +84,11 @@ java_genrule { ":FrameworksCoreTests_install_split_feature_a", ":FrameworksCoreTests_install_use_perm_good", ":FrameworksCoreTests_install_uses_feature", + ":FrameworksCoreTests_install_uses_sdk_0", + ":FrameworksCoreTests_install_uses_sdk_q0", + ":FrameworksCoreTests_install_uses_sdk_r", + ":FrameworksCoreTests_install_uses_sdk_r0", + ":FrameworksCoreTests_install_uses_sdk_r5", ":FrameworksCoreTests_install_verifier_bad", ":FrameworksCoreTests_install_verifier_good", ":FrameworksCoreTests_keyset_permdef_sa_unone", diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index 1aea98a93afe..b85a332e9000 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -95,6 +95,7 @@ <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" /> <uses-permission android:name="android.permission.KILL_UID" /> + <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES" /> <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" /> diff --git a/core/tests/coretests/AndroidTest.xml b/core/tests/coretests/AndroidTest.xml index b40aa87cb78b..ed9d3f54ef3d 100644 --- a/core/tests/coretests/AndroidTest.xml +++ b/core/tests/coretests/AndroidTest.xml @@ -21,6 +21,8 @@ <option name="cleanup-apks" value="true" /> <option name="test-file-name" value="FrameworksCoreTests.apk" /> <option name="test-file-name" value="BstatsTestApp.apk" /> + <option name="test-file-name" value="BinderDeathRecipientHelperApp1.apk" /> + <option name="test-file-name" value="BinderDeathRecipientHelperApp2.apk" /> </target_preparer> <option name="test-tag" value="FrameworksCoreTests" /> <test class="com.android.tradefed.testtype.AndroidJUnitTest" > diff --git a/core/tests/coretests/BinderDeathRecipientHelperApp/Android.bp b/core/tests/coretests/BinderDeathRecipientHelperApp/Android.bp new file mode 100644 index 000000000000..25e4fc366124 --- /dev/null +++ b/core/tests/coretests/BinderDeathRecipientHelperApp/Android.bp @@ -0,0 +1,19 @@ +android_test_helper_app { + name: "BinderDeathRecipientHelperApp1", + + srcs: ["**/*.java"], + + sdk_version: "current", +} + +android_test_helper_app { + name: "BinderDeathRecipientHelperApp2", + + srcs: ["**/*.java"], + + sdk_version: "current", + + aaptflags: [ + "--rename-manifest-package com.android.frameworks.coretests.bdr_helper_app2", + ], +} diff --git a/core/tests/coretests/BinderDeathRecipientHelperApp/AndroidManifest.xml b/core/tests/coretests/BinderDeathRecipientHelperApp/AndroidManifest.xml new file mode 100644 index 000000000000..dbd1774b7d79 --- /dev/null +++ b/core/tests/coretests/BinderDeathRecipientHelperApp/AndroidManifest.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.frameworks.coretests.bdr_helper_app1"> + + <application> + <receiver android:name="com.android.frameworks.coretests.bdr_helper_app.TestCommsReceiver" + android:exported="true"/> + + </application> +</manifest> diff --git a/core/tests/coretests/BinderDeathRecipientHelperApp/src/com/android/frameworks/coretests/bdr_helper_app/TestCommsReceiver.java b/core/tests/coretests/BinderDeathRecipientHelperApp/src/com/android/frameworks/coretests/bdr_helper_app/TestCommsReceiver.java new file mode 100644 index 000000000000..ab79e69d1351 --- /dev/null +++ b/core/tests/coretests/BinderDeathRecipientHelperApp/src/com/android/frameworks/coretests/bdr_helper_app/TestCommsReceiver.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.frameworks.coretests.bdr_helper_app; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.Binder; +import android.os.Bundle; +import android.util.Log; + +/** + * Receiver used to hand off a binder owned by this process to + * {@link android.os.BinderDeathRecipientTest}. + */ +public class TestCommsReceiver extends BroadcastReceiver { + private static final String TAG = TestCommsReceiver.class.getSimpleName(); + private static final String PACKAGE_NAME = "com.android.frameworks.coretests.bdr_helper_app"; + + public static final String ACTION_GET_BINDER = PACKAGE_NAME + ".action.GET_BINDER"; + public static final String EXTRA_KEY_BINDER = PACKAGE_NAME + ".EXTRA_BINDER"; + + @Override + public void onReceive(Context context, Intent intent) { + switch (intent.getAction()) { + case ACTION_GET_BINDER: + final Bundle resultExtras = new Bundle(); + resultExtras.putBinder(EXTRA_KEY_BINDER, new Binder()); + setResult(Activity.RESULT_OK, null, resultExtras); + break; + default: + Log.e(TAG, "Unknown action " + intent.getAction()); + break; + } + } +} diff --git a/core/tests/coretests/apks/install_uses_sdk/Android.bp b/core/tests/coretests/apks/install_uses_sdk/Android.bp new file mode 100644 index 000000000000..92b09ed3818d --- /dev/null +++ b/core/tests/coretests/apks/install_uses_sdk/Android.bp @@ -0,0 +1,39 @@ +android_test_helper_app { + name: "FrameworksCoreTests_install_uses_sdk_r0", + defaults: ["FrameworksCoreTests_apks_defaults"], + manifest: "AndroidManifest-r0.xml", + + srcs: ["**/*.java"], +} + +android_test_helper_app { + name: "FrameworksCoreTests_install_uses_sdk_r5", + defaults: ["FrameworksCoreTests_apks_defaults"], + manifest: "AndroidManifest-r5.xml", + + srcs: ["**/*.java"], +} + +android_test_helper_app { + name: "FrameworksCoreTests_install_uses_sdk_q0", + defaults: ["FrameworksCoreTests_apks_defaults"], + manifest: "AndroidManifest-q0.xml", + + srcs: ["**/*.java"], +} + +android_test_helper_app { + name: "FrameworksCoreTests_install_uses_sdk_r", + defaults: ["FrameworksCoreTests_apks_defaults"], + manifest: "AndroidManifest-r.xml", + + srcs: ["**/*.java"], +} + +android_test_helper_app { + name: "FrameworksCoreTests_install_uses_sdk_0", + defaults: ["FrameworksCoreTests_apks_defaults"], + manifest: "AndroidManifest-0.xml", + + srcs: ["**/*.java"], +} diff --git a/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-0.xml b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-0.xml new file mode 100644 index 000000000000..634228b1ace3 --- /dev/null +++ b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-0.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 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.frameworks.coretests.install_uses_sdk"> + + <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29"> + <!-- This is invalid, because there is no sdk version specified --> + <extension-sdk android:minExtensionVersion="5" /> + </uses-sdk> + + <application> + </application> +</manifest> diff --git a/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-q0.xml b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-q0.xml new file mode 100644 index 000000000000..8994966832aa --- /dev/null +++ b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-q0.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 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.frameworks.coretests.install_uses_sdk"> + + <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29"> + <!-- This fails because 29 doesn't have an extension sdk --> + <extension-sdk android:sdkVersion="29" android:minExtensionVersion="0" /> + </uses-sdk> + + <application> + </application> +</manifest> diff --git a/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r.xml b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r.xml new file mode 100644 index 000000000000..0d0d8b9e9029 --- /dev/null +++ b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 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.frameworks.coretests.install_uses_sdk"> + + <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29"> + <!-- This is invalid, because there is no minimum extension version specified --> + <extension-sdk android:sdkVersion="10000" /> + </uses-sdk> + + <application> + </application> +</manifest> diff --git a/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r0.xml b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r0.xml new file mode 100644 index 000000000000..a987afa2438e --- /dev/null +++ b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r0.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 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.frameworks.coretests.install_uses_sdk"> + + <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29"> + <extension-sdk android:sdkVersion="10000" android:minExtensionVersion="0" /> + </uses-sdk> + + <application> + </application> +</manifest> diff --git a/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r5.xml b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r5.xml new file mode 100644 index 000000000000..9860096386bc --- /dev/null +++ b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r5.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 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.frameworks.coretests.install_uses_sdk"> + + <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29"> + <!-- This will fail to install, because minExtensionVersion is not met --> + <extension-sdk android:sdkVersion="10000" android:minExtensionVersion="5" /> + </uses-sdk> + + <application> + </application> +</manifest> diff --git a/core/tests/coretests/apks/install_uses_sdk/res/values/strings.xml b/core/tests/coretests/apks/install_uses_sdk/res/values/strings.xml new file mode 100644 index 000000000000..3b8b3b1af9b5 --- /dev/null +++ b/core/tests/coretests/apks/install_uses_sdk/res/values/strings.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- Just need this dummy file to have something to build. --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="dummy">dummy</string> +</resources> diff --git a/core/tests/coretests/src/android/app/appsearch/AppSearchDocumentTest.java b/core/tests/coretests/src/android/app/appsearch/AppSearchDocumentTest.java new file mode 100644 index 000000000000..2091d556394d --- /dev/null +++ b/core/tests/coretests/src/android/app/appsearch/AppSearchDocumentTest.java @@ -0,0 +1,211 @@ +/* + * 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.app.appsearch; + +import static com.google.common.truth.Truth.assertThat; + +import static org.testng.Assert.assertThrows; + +import android.app.appsearch.AppSearch.Document; + +import androidx.test.filters.SmallTest; + +import com.google.android.icing.proto.DocumentProto; +import com.google.android.icing.proto.PropertyProto; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +@SmallTest +public class AppSearchDocumentTest { + + @Test + public void testDocumentEquals_Identical() { + Document document1 = Document.newBuilder("uri1", "schemaType1") + .setCreationTimestampSecs(0L) + .setProperty("longKey1", 1L, 2L, 3L) + .setProperty("doubleKey1", 1.0, 2.0, 3.0) + .setProperty("booleanKey1", true, false, true) + .setProperty("stringKey1", "test-value1", "test-value2", "test-value3") + .build(); + Document document2 = Document.newBuilder("uri1", "schemaType1") + .setCreationTimestampSecs(0L) + .setProperty("longKey1", 1L, 2L, 3L) + .setProperty("doubleKey1", 1.0, 2.0, 3.0) + .setProperty("booleanKey1", true, false, true) + .setProperty("stringKey1", "test-value1", "test-value2", "test-value3") + .build(); + assertThat(document1).isEqualTo(document2); + assertThat(document1.hashCode()).isEqualTo(document2.hashCode()); + } + + @Test + public void testDocumentEquals_DifferentOrder() { + Document document1 = Document.newBuilder("uri1", "schemaType1") + .setCreationTimestampSecs(0L) + .setProperty("longKey1", 1L, 2L, 3L) + .setProperty("doubleKey1", 1.0, 2.0, 3.0) + .setProperty("booleanKey1", true, false, true) + .setProperty("stringKey1", "test-value1", "test-value2", "test-value3") + .build(); + + // Create second document with same parameter but different order. + Document document2 = Document.newBuilder("uri1", "schemaType1") + .setCreationTimestampSecs(0L) + .setProperty("booleanKey1", true, false, true) + .setProperty("stringKey1", "test-value1", "test-value2", "test-value3") + .setProperty("doubleKey1", 1.0, 2.0, 3.0) + .setProperty("longKey1", 1L, 2L, 3L) + .build(); + assertThat(document1).isEqualTo(document2); + assertThat(document1.hashCode()).isEqualTo(document2.hashCode()); + } + + @Test + public void testDocumentEquals_Failure() { + Document document1 = Document.newBuilder("uri1", "schemaType1") + .setProperty("longKey1", 1L, 2L, 3L) + .build(); + + // Create second document with same order but different value. + Document document2 = Document.newBuilder("uri1", "schemaType1") + .setProperty("longKey1", 1L, 2L, 4L) // Different + .build(); + assertThat(document1).isNotEqualTo(document2); + assertThat(document1.hashCode()).isNotEqualTo(document2.hashCode()); + } + + @Test + public void testDocumentEquals_Failure_RepeatedFieldOrder() { + Document document1 = Document.newBuilder("uri1", "schemaType1") + .setProperty("booleanKey1", true, false, true) + .build(); + + // Create second document with same order but different value. + Document document2 = Document.newBuilder("uri1", "schemaType1") + .setProperty("booleanKey1", true, true, false) // Different + .build(); + assertThat(document1).isNotEqualTo(document2); + assertThat(document1.hashCode()).isNotEqualTo(document2.hashCode()); + } + + @Test + public void testDocumentGetSingleValue() { + Document document = Document.newBuilder("uri1", "schemaType1") + .setProperty("longKey1", 1L) + .setProperty("doubleKey1", 1.0) + .setProperty("booleanKey1", true) + .setProperty("stringKey1", "test-value1").build(); + assertThat(document.getUri()).isEqualTo("uri1"); + assertThat(document.getSchemaType()).isEqualTo("schemaType1"); + assertThat(document.getPropertyLong("longKey1")).isEqualTo(1L); + assertThat(document.getPropertyDouble("doubleKey1")).isEqualTo(1.0); + assertThat(document.getPropertyBoolean("booleanKey1")).isTrue(); + assertThat(document.getPropertyString("stringKey1")).isEqualTo("test-value1"); + } + + @Test + public void testDocumentGetArrayValues() { + Document document = Document.newBuilder("uri1", "schemaType1") + .setScore(1) + .setProperty("longKey1", 1L, 2L, 3L) + .setProperty("doubleKey1", 1.0, 2.0, 3.0) + .setProperty("booleanKey1", true, false, true) + .setProperty("stringKey1", "test-value1", "test-value2", "test-value3") + .build(); + + assertThat(document.getUri()).isEqualTo("uri1"); + assertThat(document.getSchemaType()).isEqualTo("schemaType1"); + assertThat(document.getScore()).isEqualTo(1); + assertThat(document.getPropertyLongArray("longKey1")).asList().containsExactly(1L, 2L, 3L); + assertThat(document.getPropertyDoubleArray("doubleKey1")).usingExactEquality() + .containsExactly(1.0, 2.0, 3.0); + assertThat(document.getPropertyBooleanArray("booleanKey1")).asList() + .containsExactly(true, false, true); + assertThat(document.getPropertyStringArray("stringKey1")).asList() + .containsExactly("test-value1", "test-value2", "test-value3"); + } + + @Test + public void testDocumentGetValues_DifferentTypes() { + Document document = Document.newBuilder("uri1", "schemaType1") + .setScore(1) + .setProperty("longKey1", 1L) + .setProperty("booleanKey1", true, false, true) + .setProperty("stringKey1", "test-value1", "test-value2", "test-value3") + .build(); + + // Get a value for a key that doesn't exist + assertThat(document.getPropertyDouble("doubleKey1")).isNull(); + assertThat(document.getPropertyDoubleArray("doubleKey1")).isNull(); + + // Get a value with a single element as an array and as a single value + assertThat(document.getPropertyLong("longKey1")).isEqualTo(1L); + assertThat(document.getPropertyLongArray("longKey1")).asList().containsExactly(1L); + + // Get a value with multiple elements as an array and as a single value + assertThat(document.getPropertyString("stringKey1")).isEqualTo("test-value1"); + assertThat(document.getPropertyStringArray("stringKey1")).asList() + .containsExactly("test-value1", "test-value2", "test-value3"); + + // Get a value of the wrong type + assertThat(document.getPropertyDouble("longKey1")).isNull(); + assertThat(document.getPropertyDoubleArray("longKey1")).isNull(); + } + + @Test + public void testDocumentInvalid() { + Document.Builder builder = Document.newBuilder("uri1", "schemaType1"); + assertThrows( + IllegalArgumentException.class, () -> builder.setProperty("test", new boolean[]{})); + } + + @Test + public void testDocumentProtoPopulation() { + Document document = Document.newBuilder("uri1", "schemaType1") + .setScore(1) + .setCreationTimestampSecs(0) + .setProperty("longKey1", 1L) + .setProperty("doubleKey1", 1.0) + .setProperty("booleanKey1", true) + .setProperty("stringKey1", "test-value1") + .build(); + + // Create the Document proto. Need to sort the property order by key. + DocumentProto.Builder documentProtoBuilder = DocumentProto.newBuilder() + .setUri("uri1").setSchema("schemaType1").setScore(1).setCreationTimestampSecs(0); + HashMap<String, PropertyProto.Builder> propertyProtoMap = new HashMap<>(); + propertyProtoMap.put("longKey1", + PropertyProto.newBuilder().setName("longKey1").addInt64Values(1L)); + propertyProtoMap.put("doubleKey1", + PropertyProto.newBuilder().setName("doubleKey1").addDoubleValues(1.0)); + propertyProtoMap.put("booleanKey1", + PropertyProto.newBuilder().setName("booleanKey1").addBooleanValues(true)); + propertyProtoMap.put("stringKey1", + PropertyProto.newBuilder().setName("stringKey1").addStringValues("test-value1")); + List<String> sortedKey = new ArrayList<>(propertyProtoMap.keySet()); + Collections.sort(sortedKey); + for (String key : sortedKey) { + documentProtoBuilder.addProperties(propertyProtoMap.get(key)); + } + assertThat(document.getProto()).isEqualTo(documentProtoBuilder.build()); + } +} diff --git a/core/tests/coretests/src/android/app/appsearch/AppSearchEmailTest.java b/core/tests/coretests/src/android/app/appsearch/AppSearchEmailTest.java new file mode 100644 index 000000000000..c50b1da71d02 --- /dev/null +++ b/core/tests/coretests/src/android/app/appsearch/AppSearchEmailTest.java @@ -0,0 +1,55 @@ +/* + * 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.app.appsearch; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.appsearch.AppSearch.Email; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; + +@SmallTest +public class AppSearchEmailTest { + + @Test + public void testBuildEmailAndGetValue() { + Email email = Email.newBuilder("uri") + .setFrom("FakeFromAddress") + .setCc("CC1", "CC2") + // Score and Property are mixed into the middle to make sure DocumentBuilder's + // methods can be interleaved with EmailBuilder's methods. + .setScore(1) + .setProperty("propertyKey", "propertyValue1", "propertyValue2") + .setSubject("subject") + .setBody("EmailBody") + .build(); + + assertThat(email.getUri()).isEqualTo("uri"); + assertThat(email.getFrom()).isEqualTo("FakeFromAddress"); + assertThat(email.getTo()).isNull(); + assertThat(email.getCc()).asList().containsExactly("CC1", "CC2"); + assertThat(email.getBcc()).isNull(); + assertThat(email.getScore()).isEqualTo(1); + assertThat(email.getPropertyString("propertyKey")).isEqualTo("propertyValue1"); + assertThat(email.getPropertyStringArray("propertyKey")).asList().containsExactly( + "propertyValue1", "propertyValue2"); + assertThat(email.getSubject()).isEqualTo("subject"); + assertThat(email.getBody()).isEqualTo("EmailBody"); + } +} diff --git a/core/tests/coretests/src/android/app/appsearch/AppSearchSchemaTest.java b/core/tests/coretests/src/android/app/appsearch/AppSearchSchemaTest.java new file mode 100644 index 000000000000..0be52c180338 --- /dev/null +++ b/core/tests/coretests/src/android/app/appsearch/AppSearchSchemaTest.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.appsearch; + +import static com.google.common.truth.Truth.assertThat; + +import static org.testng.Assert.assertThrows; +import static org.testng.Assert.expectThrows; + +import android.app.appsearch.AppSearchSchema.IndexingConfig; +import android.app.appsearch.AppSearchSchema.PropertyConfig; + +import androidx.test.filters.SmallTest; + +import com.google.android.icing.proto.IndexingConfig.TokenizerType; +import com.google.android.icing.proto.PropertyConfigProto; +import com.google.android.icing.proto.SchemaProto; +import com.google.android.icing.proto.SchemaTypeConfigProto; +import com.google.android.icing.proto.TermMatchType; + +import org.junit.Test; + +@SmallTest +public class AppSearchSchemaTest { + @Test + public void testSuccess() { + AppSearchSchema schema = AppSearchSchema.newBuilder() + .addType(AppSearchSchema.newSchemaTypeBuilder("Email") + .addProperty(AppSearchSchema.newPropertyBuilder("subject") + .setDataType(PropertyConfig.DATA_TYPE_STRING) + .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL) + .setIndexingConfig(AppSearchSchema.newIndexingConfigBuilder() + .setTokenizerType(IndexingConfig.TOKENIZER_TYPE_PLAIN) + .setTermMatchType(IndexingConfig.TERM_MATCH_TYPE_PREFIX) + .build() + ).build() + ).addProperty(AppSearchSchema.newPropertyBuilder("body") + .setDataType(PropertyConfig.DATA_TYPE_STRING) + .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL) + .setIndexingConfig(AppSearchSchema.newIndexingConfigBuilder() + .setTokenizerType(IndexingConfig.TOKENIZER_TYPE_PLAIN) + .setTermMatchType(IndexingConfig.TERM_MATCH_TYPE_PREFIX) + .build() + ).build() + ).build() + + ).addType(AppSearchSchema.newSchemaTypeBuilder("MusicRecording") + .addProperty(AppSearchSchema.newPropertyBuilder("artist") + .setDataType(PropertyConfig.DATA_TYPE_STRING) + .setCardinality(PropertyConfig.CARDINALITY_REPEATED) + .setIndexingConfig(AppSearchSchema.newIndexingConfigBuilder() + .setTokenizerType(IndexingConfig.TOKENIZER_TYPE_PLAIN) + .setTermMatchType(IndexingConfig.TERM_MATCH_TYPE_PREFIX) + .build() + ).build() + ).addProperty(AppSearchSchema.newPropertyBuilder("pubDate") + .setDataType(PropertyConfig.DATA_TYPE_INT64) + .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL) + .setIndexingConfig(AppSearchSchema.newIndexingConfigBuilder() + .setTokenizerType(IndexingConfig.TOKENIZER_TYPE_NONE) + .setTermMatchType(IndexingConfig.TERM_MATCH_TYPE_UNKNOWN) + .build() + ).build() + ).build() + ).build(); + + SchemaProto expectedProto = SchemaProto.newBuilder() + .addTypes(SchemaTypeConfigProto.newBuilder() + .setSchemaType("Email") + .addProperties(PropertyConfigProto.newBuilder() + .setPropertyName("subject") + .setDataType(PropertyConfigProto.DataType.Code.STRING) + .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL) + .setIndexingConfig( + com.google.android.icing.proto.IndexingConfig.newBuilder() + .setTokenizerType(TokenizerType.Code.PLAIN) + .setTermMatchType(TermMatchType.Code.PREFIX) + ) + ).addProperties(PropertyConfigProto.newBuilder() + .setPropertyName("body") + .setDataType(PropertyConfigProto.DataType.Code.STRING) + .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL) + .setIndexingConfig( + com.google.android.icing.proto.IndexingConfig.newBuilder() + .setTokenizerType(TokenizerType.Code.PLAIN) + .setTermMatchType(TermMatchType.Code.PREFIX) + ) + ) + + ).addTypes(SchemaTypeConfigProto.newBuilder() + .setSchemaType("MusicRecording") + .addProperties(PropertyConfigProto.newBuilder() + .setPropertyName("artist") + .setDataType(PropertyConfigProto.DataType.Code.STRING) + .setCardinality(PropertyConfigProto.Cardinality.Code.REPEATED) + .setIndexingConfig( + com.google.android.icing.proto.IndexingConfig.newBuilder() + .setTokenizerType(TokenizerType.Code.PLAIN) + .setTermMatchType(TermMatchType.Code.PREFIX) + ) + ).addProperties(PropertyConfigProto.newBuilder() + .setPropertyName("pubDate") + .setDataType(PropertyConfigProto.DataType.Code.INT64) + .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL) + .setIndexingConfig( + com.google.android.icing.proto.IndexingConfig.newBuilder() + .setTokenizerType(TokenizerType.Code.NONE) + .setTermMatchType(TermMatchType.Code.UNKNOWN) + ) + ) + ).build(); + + assertThat(schema.getProto()).isEqualTo(expectedProto); + } + + @Test + public void testInvalidEnums() { + PropertyConfig.Builder builder = AppSearchSchema.newPropertyBuilder("test"); + assertThrows(IllegalArgumentException.class, () -> builder.setDataType(99)); + assertThrows(IllegalArgumentException.class, () -> builder.setCardinality(99)); + } + + @Test + public void testMissingFields() { + PropertyConfig.Builder builder = AppSearchSchema.newPropertyBuilder("test"); + Exception e = expectThrows(IllegalSchemaException.class, builder::build); + assertThat(e).hasMessageThat().contains("Missing field: dataType"); + + builder.setDataType(PropertyConfig.DATA_TYPE_DOCUMENT); + e = expectThrows(IllegalSchemaException.class, builder::build); + assertThat(e).hasMessageThat().contains("Missing field: schemaType"); + + builder.setSchemaType("TestType"); + e = expectThrows(IllegalSchemaException.class, builder::build); + assertThat(e).hasMessageThat().contains("Missing field: cardinality"); + + builder.setCardinality(PropertyConfig.CARDINALITY_REPEATED); + builder.build(); + } +} diff --git a/core/tests/coretests/src/android/app/appsearch/impl/CustomerDocumentTest.java b/core/tests/coretests/src/android/app/appsearch/impl/CustomerDocumentTest.java new file mode 100644 index 000000000000..4ee4aa6d527f --- /dev/null +++ b/core/tests/coretests/src/android/app/appsearch/impl/CustomerDocumentTest.java @@ -0,0 +1,84 @@ +/* + * 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.app.appsearch.impl; + +import static com.google.common.truth.Truth.assertThat; + +import android.annotation.NonNull; +import android.app.appsearch.AppSearch.Document; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; + +/** Tests that {@link Document} and {@link Document.Builder} are extendable by developers. + * + * <p>This class is intentionally in a different package than {@link Document} to make sure there + * are no package-private methods required for external developers to add custom types. + */ +@SmallTest +public class CustomerDocumentTest { + @Test + public void testBuildCustomerDocument() { + CustomerDocument customerDocument = CustomerDocument.newBuilder("uri1") + .setScore(1) + .setCreationTimestampSecs(0) + .setProperty("longKey1", 1L, 2L, 3L) + .setProperty("doubleKey1", 1.0, 2.0, 3.0) + .setProperty("booleanKey1", true, false, true) + .setProperty("stringKey1", "test-value1", "test-value2", "test-value3") + .build(); + + assertThat(customerDocument.getUri()).isEqualTo("uri1"); + assertThat(customerDocument.getSchemaType()).isEqualTo("customerDocument"); + assertThat(customerDocument.getScore()).isEqualTo(1); + assertThat(customerDocument.getCreationTimestampSecs()).isEqualTo(0L); + assertThat(customerDocument.getPropertyLongArray("longKey1")).asList() + .containsExactly(1L, 2L, 3L); + assertThat(customerDocument.getPropertyDoubleArray("doubleKey1")).usingExactEquality() + .containsExactly(1.0, 2.0, 3.0); + assertThat(customerDocument.getPropertyBooleanArray("booleanKey1")).asList() + .containsExactly(true, false, true); + assertThat(customerDocument.getPropertyStringArray("stringKey1")).asList() + .containsExactly("test-value1", "test-value2", "test-value3"); + } + + /** + * An example document type for test purposes, defined outside of + * {@link android.app.appsearch.AppSearch} (the way an external developer would define it). + */ + private static class CustomerDocument extends Document { + private CustomerDocument(Document document) { + super(document); + } + + public static CustomerDocument.Builder newBuilder(String uri) { + return new CustomerDocument.Builder(uri); + } + + public static class Builder extends Document.Builder<CustomerDocument.Builder> { + private Builder(@NonNull String uri) { + super(uri, "customerDocument"); + } + + @Override + public CustomerDocument build() { + return new CustomerDocument(super.build()); + } + } + } +} diff --git a/core/tests/coretests/src/android/content/pm/PackageParserTest.java b/core/tests/coretests/src/android/content/pm/PackageParserTest.java index 440219018b92..dfd762b17307 100644 --- a/core/tests/coretests/src/android/content/pm/PackageParserTest.java +++ b/core/tests/coretests/src/android/content/pm/PackageParserTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import android.apex.ApexInfo; import android.content.Context; @@ -486,4 +487,34 @@ public class PackageParserTest { assertTrue((pi.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0); assertTrue((pi.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0); } + + @Test + public void testUsesSdk() throws Exception { + parsePackage("install_uses_sdk.apk_r0", R.raw.install_uses_sdk_r0, x -> x); + try { + parsePackage("install_uses_sdk.apk_r5", R.raw.install_uses_sdk_r5, x -> x); + fail("Expected parsing exception due to incompatible extension SDK version"); + } catch (PackageParser.PackageParserException expected) { + assertEquals(PackageManager.INSTALL_FAILED_OLDER_SDK, expected.error); + } + try { + parsePackage("install_uses_sdk.apk_q0", R.raw.install_uses_sdk_q0, x -> x); + fail("Expected parsing exception due to non-existent extension SDK"); + } catch (PackageParser.PackageParserException expected) { + assertEquals(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, expected.error); + } + try { + parsePackage("install_uses_sdk.apk_r", R.raw.install_uses_sdk_r, x -> x); + fail("Expected parsing exception due to unspecified extension SDK version"); + } catch (PackageParser.PackageParserException expected) { + assertEquals(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, expected.error); + } + try { + parsePackage("install_uses_sdk.apk_0", R.raw.install_uses_sdk_0, x -> x); + fail("Expected parsing exception due to unspecified extension SDK"); + } catch (PackageParser.PackageParserException expected) { + assertEquals(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, expected.error); + } + + } } diff --git a/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java b/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java new file mode 100644 index 000000000000..2cce43f70774 --- /dev/null +++ b/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java @@ -0,0 +1,162 @@ +/* + * 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.os; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.app.Activity; +import android.app.ActivityManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.ArraySet; +import android.util.Log; +import android.util.Pair; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.android.frameworks.coretests.bdr_helper_app.TestCommsReceiver; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Set; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Tests functionality of {@link android.os.IBinder.DeathRecipient} callbacks. + */ +@RunWith(AndroidJUnit4.class) +public class BinderDeathRecipientTest { + private static final String TAG = BinderDeathRecipientTest.class.getSimpleName(); + private static final String TEST_PACKAGE_NAME_1 = + "com.android.frameworks.coretests.bdr_helper_app1"; + private static final String TEST_PACKAGE_NAME_2 = + "com.android.frameworks.coretests.bdr_helper_app2"; + + private Context mContext; + private Handler mHandler; + private ActivityManager mActivityManager; + private Set<Pair<IBinder, IBinder.DeathRecipient>> mLinkedDeathRecipients = new ArraySet<>(); + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getTargetContext(); + mActivityManager = mContext.getSystemService(ActivityManager.class); + mHandler = new Handler(Looper.getMainLooper()); + } + + private IBinder getNewRemoteBinder(String testPackage) throws InterruptedException { + final CountDownLatch resultLatch = new CountDownLatch(1); + final AtomicInteger resultCode = new AtomicInteger(Activity.RESULT_CANCELED); + final AtomicReference<Bundle> resultExtras = new AtomicReference<>(); + + final Intent intent = new Intent(TestCommsReceiver.ACTION_GET_BINDER) + .setClassName(testPackage, TestCommsReceiver.class.getName()); + mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + resultCode.set(getResultCode()); + resultExtras.set(getResultExtras(true)); + resultLatch.countDown(); + } + }, mHandler, Activity.RESULT_CANCELED, null, null); + + assertTrue("Request for binder timed out", resultLatch.await(5, TimeUnit.SECONDS)); + assertEquals(Activity.RESULT_OK, resultCode.get()); + return resultExtras.get().getBinder(TestCommsReceiver.EXTRA_KEY_BINDER); + } + + @Test + public void binderDied_noArgs() throws Exception { + final IBinder testAppBinder = getNewRemoteBinder(TEST_PACKAGE_NAME_1); + final CountDownLatch deathNotificationLatch = new CountDownLatch(1); + final IBinder.DeathRecipient simpleDeathRecipient = + () -> deathNotificationLatch.countDown(); + testAppBinder.linkToDeath(simpleDeathRecipient, 0); + mLinkedDeathRecipients.add(Pair.create(testAppBinder, simpleDeathRecipient)); + + mActivityManager.forceStopPackage(TEST_PACKAGE_NAME_1); + assertTrue("Death notification did not arrive", + deathNotificationLatch.await(10, TimeUnit.SECONDS)); + } + + @Test + public void binderDied_iBinderArg() throws Exception { + final IBinder testApp1Binder = getNewRemoteBinder(TEST_PACKAGE_NAME_1); + final IBinder testApp2Binder = getNewRemoteBinder(TEST_PACKAGE_NAME_2); + final CyclicBarrier barrier = new CyclicBarrier(2); + + final AtomicReference<IBinder> binderThatDied = new AtomicReference<>(); + final IBinder.DeathRecipient sameDeathRecipient = new IBinder.DeathRecipient() { + @Override + public void binderDied() { + Log.e(TAG, "Should not have been called!"); + } + + @Override + public void binderDied(IBinder who) { + binderThatDied.set(who); + try { + barrier.await(); + } catch (InterruptedException | BrokenBarrierException e) { + Log.e(TAG, "Unexpected exception while waiting on CyclicBarrier", e); + } + } + }; + testApp1Binder.linkToDeath(sameDeathRecipient, 0); + mLinkedDeathRecipients.add(Pair.create(testApp1Binder, sameDeathRecipient)); + + testApp2Binder.linkToDeath(sameDeathRecipient, 0); + mLinkedDeathRecipients.add(Pair.create(testApp2Binder, sameDeathRecipient)); + + mActivityManager.forceStopPackage(TEST_PACKAGE_NAME_1); + try { + barrier.await(10, TimeUnit.SECONDS); + } catch (TimeoutException e) { + fail("Timed out while waiting for 1st death notification: " + e.getMessage()); + } + assertEquals("Different binder received", testApp1Binder, binderThatDied.get()); + + barrier.reset(); + mActivityManager.forceStopPackage(TEST_PACKAGE_NAME_2); + try { + barrier.await(10, TimeUnit.SECONDS); + } catch (TimeoutException e) { + fail("Timed out while waiting for 2nd death notification: " + e.getMessage()); + } + assertEquals("Different binder received", testApp2Binder, binderThatDied.get()); + } + + @After + public void tearDown() { + for (Pair<IBinder, IBinder.DeathRecipient> linkedPair : mLinkedDeathRecipients) { + linkedPair.first.unlinkToDeath(linkedPair.second, 0); + } + } +} diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index 1db96b15f83a..628f7ecce9f1 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -16,9 +16,13 @@ package android.view; +import static android.view.InsetsController.ANIMATION_TYPE_HIDE; +import static android.view.InsetsController.ANIMATION_TYPE_NONE; +import static android.view.InsetsController.ANIMATION_TYPE_SHOW; import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; +import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; import static android.view.WindowInsets.Type.statusBars; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; @@ -40,12 +44,15 @@ import android.platform.test.annotations.Presubmit; import android.view.WindowInsets.Type; import android.view.WindowManager.BadTokenException; import android.view.WindowManager.LayoutParams; +import android.view.test.InsetsModeSession; import android.widget.TextView; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; +import org.junit.AfterClass; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -69,6 +76,17 @@ public class InsetsControllerTest { private SurfaceSession mSession = new SurfaceSession(); private SurfaceControl mLeash; private ViewRootImpl mViewRoot; + private static InsetsModeSession sInsetsModeSession; + + @BeforeClass + public static void setupOnce() { + sInsetsModeSession = new InsetsModeSession(NEW_INSETS_MODE_FULL); + } + + @AfterClass + public static void tearDownOnce() { + sInsetsModeSession.close(); + } @Before public void setup() { @@ -86,6 +104,11 @@ public class InsetsControllerTest { } mController = new InsetsController(mViewRoot); final Rect rect = new Rect(5, 5, 5, 5); + mController.getState().getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 10)); + mController.getState().getSource(ITYPE_NAVIGATION_BAR).setFrame( + new Rect(0, 90, 100, 100)); + mController.getState().getSource(ITYPE_IME).setFrame(new Rect(0, 50, 100, 100)); + mController.getState().setDisplayFrame(new Rect(0, 0, 100, 100)); mController.calculateInsets( false, false, @@ -93,7 +116,6 @@ public class InsetsControllerTest { Insets.of(10, 10, 10, 10), rect, rect, rect, rect), rect, rect, SOFT_INPUT_ADJUST_RESIZE); mController.onFrameChanged(new Rect(0, 0, 100, 100)); - mController.getState().setDisplayFrame(new Rect(0, 0, 100, 100)); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } @@ -205,18 +227,24 @@ public class InsetsControllerTest { InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { int types = Type.navigationBars() | Type.systemBars(); - // test show select types. - mController.show(types); + // test hide select types. + mController.hide(types); + assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR)); + assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR)); mController.cancelExistingAnimation(); - assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); - assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_NAVIGATION_BAR)); + assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_STATUS_BAR)); + assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); // test hide all - mController.hide(types); + mController.show(types); + assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_NAVIGATION_BAR)); + assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_STATUS_BAR)); mController.cancelExistingAnimation(); - assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); - assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -271,30 +299,38 @@ public class InsetsControllerTest { InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { // start two animations and see if previous is cancelled and final state is reached. - mController.show(Type.navigationBars()); - mController.show(Type.systemBars()); - mController.cancelExistingAnimation(); - assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); - assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); - mController.hide(Type.navigationBars()); mController.hide(Type.systemBars()); + assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR)); + assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR)); mController.cancelExistingAnimation(); assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); + mController.show(Type.navigationBars()); + mController.show(Type.systemBars()); + assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_NAVIGATION_BAR)); + assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_STATUS_BAR)); + mController.cancelExistingAnimation(); + assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); + int types = Type.navigationBars() | Type.systemBars(); // show two at a time and hide one by one. mController.show(types); mController.hide(Type.navigationBars()); + assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR)); + assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_STATUS_BAR)); mController.cancelExistingAnimation(); assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); mController.hide(Type.systemBars()); + assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_NAVIGATION_BAR)); + assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR)); mController.cancelExistingAnimation(); assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java index 1bd52af09a4d..ade1e0de7102 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java @@ -46,7 +46,7 @@ public class AccessibilityNodeInfoTest { // The number of fields tested in the corresponding CTS AccessibilityNodeInfoTest: // See fullyPopulateAccessibilityNodeInfo, assertEqualsAccessibilityNodeInfo, // and assertAccessibilityNodeInfoCleared in that class. - private static final int NUM_MARSHALLED_PROPERTIES = 35; + private static final int NUM_MARSHALLED_PROPERTIES = 38; /** * The number of properties that are purposely not marshalled diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java index 93f4b5143c57..f151b810eea3 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java @@ -19,9 +19,11 @@ package android.view.accessibility; import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.IAccessibilityServiceConnection; import android.content.pm.ParceledListSlice; +import android.graphics.Bitmap; import android.graphics.Region; import android.os.Bundle; import android.os.IBinder; +import android.os.RemoteCallback; /** * Stub implementation of IAccessibilityServiceConnection so each test doesn't need to implement @@ -143,4 +145,14 @@ public class AccessibilityServiceConnectionImpl extends IAccessibilityServiceCon public IBinder getOverlayWindowToken(int displayId) { return null; } + + public int getWindowIdForLeashToken(IBinder token) { + return -1; + } + + public Bitmap takeScreenshot(int displayId) { + return null; + } + + public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {} } diff --git a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java index 89c237498e5c..a602fa31281f 100644 --- a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java +++ b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java @@ -32,6 +32,7 @@ import static org.junit.Assert.assertTrue; import android.app.Activity; import android.app.Instrumentation; +import android.view.InputDevice; import android.view.MotionEvent; import androidx.test.InstrumentationRegistry; @@ -67,6 +68,7 @@ public class EditorCursorDragTest { mOriginalFlagValue = Editor.FLAG_ENABLE_CURSOR_DRAG; Editor.FLAG_ENABLE_CURSOR_DRAG = true; } + @After public void after() throws Throwable { Editor.FLAG_ENABLE_CURSOR_DRAG = mOriginalFlagValue; @@ -281,6 +283,35 @@ public class EditorCursorDragTest { } @Test + public void testEditor_onTouchEvent_mouseDrag() throws Throwable { + String text = "testEditor_onTouchEvent_mouseDrag"; + onView(withId(R.id.textview)).perform(replaceText(text)); + onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0)); + + TextView tv = mActivity.findViewById(R.id.textview); + Editor editor = tv.getEditorForTesting(); + + // Simulate a mouse click and drag. This should NOT trigger a cursor drag. + long event1Time = 1001; + MotionEvent event1 = mouseDownEvent(event1Time, event1Time, 20f, 30f); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1)); + assertFalse(editor.getInsertionController().isCursorBeingModified()); + assertFalse(editor.getSelectionController().isCursorBeingModified()); + + long event2Time = 1002; + MotionEvent event2 = mouseMoveEvent(event1Time, event2Time, 120f, 30f); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2)); + assertFalse(editor.getInsertionController().isCursorBeingModified()); + assertTrue(editor.getSelectionController().isCursorBeingModified()); + + long event3Time = 1003; + MotionEvent event3 = mouseUpEvent(event1Time, event3Time, 120f, 30f); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3)); + assertFalse(editor.getInsertionController().isCursorBeingModified()); + assertFalse(editor.getSelectionController().isCursorBeingModified()); + } + + @Test public void testEditor_onTouchEvent_cursorDrag() throws Throwable { String text = "testEditor_onTouchEvent_cursorDrag"; onView(withId(R.id.textview)).perform(replaceText(text)); @@ -356,6 +387,23 @@ public class EditorCursorDragTest { assertFalse(editor.getSelectionController().isCursorBeingModified()); } + @Test // Reproduces b/147366705 + public void testCursorDrag_nonSelectableTextView() throws Throwable { + String text = "Hello world!"; + TextView tv = mActivity.findViewById(R.id.nonselectable_textview); + tv.setText(text); + Editor editor = tv.getEditorForTesting(); + + // Simulate a tap. No error should be thrown. + long event1Time = 1001; + MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1)); + + // Swipe left to right. No error should be thrown. + onView(withId(R.id.nonselectable_textview)).perform( + dragOnText(text.indexOf("llo"), text.indexOf("!"))); + } + private static MotionEvent downEvent(long downTime, long eventTime, float x, float y) { return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, x, y, 0); } @@ -367,4 +415,25 @@ public class EditorCursorDragTest { private static MotionEvent moveEvent(long downTime, long eventTime, float x, float y) { return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0); } + + private static MotionEvent mouseDownEvent(long downTime, long eventTime, float x, float y) { + MotionEvent event = downEvent(downTime, eventTime, x, y); + event.setSource(InputDevice.SOURCE_MOUSE); + event.setButtonState(MotionEvent.BUTTON_PRIMARY); + return event; + } + + private static MotionEvent mouseUpEvent(long downTime, long eventTime, float x, float y) { + MotionEvent event = upEvent(downTime, eventTime, x, y); + event.setSource(InputDevice.SOURCE_MOUSE); + event.setButtonState(0); + return event; + } + + private static MotionEvent mouseMoveEvent(long downTime, long eventTime, float x, float y) { + MotionEvent event = moveEvent(downTime, eventTime, x, y); + event.setSource(InputDevice.SOURCE_MOUSE); + event.setButtonState(MotionEvent.BUTTON_PRIMARY); + return event; + } } diff --git a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java index 215d0b800074..3dc001d68a02 100644 --- a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java +++ b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java @@ -48,24 +48,32 @@ public class EditorTouchStateTest { } @Test + public void testIsDistanceWithin() throws Exception { + assertTrue(EditorTouchState.isDistanceWithin(0, 0, 0, 0, 8)); + assertTrue(EditorTouchState.isDistanceWithin(3, 9, 5, 11, 8)); + assertTrue(EditorTouchState.isDistanceWithin(5, 11, 3, 9, 8)); + assertFalse(EditorTouchState.isDistanceWithin(5, 10, 5, 20, 8)); + } + + @Test public void testUpdate_singleTap() throws Exception { // Simulate an ACTION_DOWN event. long event1Time = 1000; MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f); mTouchState.update(event1, mConfig); - assertSingleTap(mTouchState, 20f, 30f, 0, 0, false); + assertSingleTap(mTouchState, 20f, 30f, 0, 0); // Simulate an ACTION_UP event. long event2Time = 1001; MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f); mTouchState.update(event2, mConfig); - assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false); + assertSingleTap(mTouchState, 20f, 30f, 20f, 30f); // Generate an ACTION_DOWN event whose time is after the double-tap timeout. long event3Time = event2Time + ViewConfiguration.getDoubleTapTimeout() + 1; MotionEvent event3 = downEvent(event3Time, event3Time, 22f, 33f); mTouchState.update(event3, mConfig); - assertSingleTap(mTouchState, 22f, 33f, 20f, 30f, false); + assertSingleTap(mTouchState, 22f, 33f, 20f, 30f); } @Test @@ -74,13 +82,13 @@ public class EditorTouchStateTest { long event1Time = 1000; MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f); mTouchState.update(event1, mConfig); - assertSingleTap(mTouchState, 20f, 30f, 0, 0, false); + assertSingleTap(mTouchState, 20f, 30f, 0, 0); // Simulate an ACTION_UP event. long event2Time = 1001; MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f); mTouchState.update(event2, mConfig); - assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false); + assertSingleTap(mTouchState, 20f, 30f, 20f, 30f); // Generate an ACTION_DOWN event whose time is within the double-tap timeout. long event3Time = 1002; @@ -96,13 +104,13 @@ public class EditorTouchStateTest { long event1Time = 1000; MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f); mTouchState.update(event1, mConfig); - assertSingleTap(mTouchState, 20f, 30f, 0, 0, false); + assertSingleTap(mTouchState, 20f, 30f, 0, 0); // Simulate an ACTION_UP event. long event2Time = 1001; MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f); mTouchState.update(event2, mConfig); - assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false); + assertSingleTap(mTouchState, 20f, 30f, 20f, 30f); // Generate an ACTION_DOWN event whose time is within the double-tap timeout. long event3Time = 1002; @@ -125,13 +133,13 @@ public class EditorTouchStateTest { long event1Time = 1000; MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f); mTouchState.update(event1, mConfig); - assertSingleTap(mTouchState, 20f, 30f, 0, 0, false); + assertSingleTap(mTouchState, 20f, 30f, 0, 0); // Simulate an ACTION_UP event with a delay that's longer than the double-tap timeout. long event2Time = 1000 + ViewConfiguration.getDoubleTapTimeout() + 1; MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f); mTouchState.update(event2, mConfig); - assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false); + assertSingleTap(mTouchState, 20f, 30f, 20f, 30f); // Generate an ACTION_DOWN event whose time is within the double-tap timeout when // calculated from the last ACTION_UP event time. Even though the time between the last up @@ -140,7 +148,7 @@ public class EditorTouchStateTest { long event3Time = event2Time + 1; MotionEvent event3 = downEvent(event3Time, event3Time, 22f, 33f); mTouchState.update(event3, mConfig); - assertSingleTap(mTouchState, 22f, 33f, 20f, 30f, false); + assertSingleTap(mTouchState, 22f, 33f, 20f, 30f); } @Test @@ -149,19 +157,19 @@ public class EditorTouchStateTest { long event1Time = 1000; MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f); mTouchState.update(event1, mConfig); - assertSingleTap(mTouchState, 20f, 30f, 0, 0, false); + assertSingleTap(mTouchState, 20f, 30f, 0, 0); // Simulate an ACTION_MOVE event. long event2Time = 1001; MotionEvent event2 = moveEvent(event1Time, event2Time, 200f, 31f); mTouchState.update(event2, mConfig); - assertSingleTap(mTouchState, 20f, 30f, 0, 0, true); + assertDrag(mTouchState, 20f, 30f, 0, 0, false); // Simulate an ACTION_UP event with a delay that's longer than the double-tap timeout. long event3Time = 5000; MotionEvent event3 = upEvent(event1Time, event3Time, 200f, 31f); mTouchState.update(event3, mConfig); - assertSingleTap(mTouchState, 20f, 30f, 200f, 31f, false); + assertSingleTap(mTouchState, 20f, 30f, 200f, 31f); // Generate an ACTION_DOWN event whose time is within the double-tap timeout when // calculated from the last ACTION_UP event time. Even though the time between the last up @@ -170,7 +178,7 @@ public class EditorTouchStateTest { long event4Time = event3Time + 1; MotionEvent event4 = downEvent(event4Time, event4Time, 200f, 31f); mTouchState.update(event4, mConfig); - assertSingleTap(mTouchState, 200f, 31f, 200f, 31f, false); + assertSingleTap(mTouchState, 200f, 31f, 200f, 31f); } @Test @@ -180,14 +188,14 @@ public class EditorTouchStateTest { MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f); event1.setSource(InputDevice.SOURCE_MOUSE); mTouchState.update(event1, mConfig); - assertSingleTap(mTouchState, 20f, 30f, 0, 0, false); + assertSingleTap(mTouchState, 20f, 30f, 0, 0); // Simulate an ACTION_UP event. long event2Time = 1001; MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f); event2.setSource(InputDevice.SOURCE_MOUSE); mTouchState.update(event2, mConfig); - assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false); + assertSingleTap(mTouchState, 20f, 30f, 20f, 30f); // Generate a second ACTION_DOWN event whose time is within the double-tap timeout. long event3Time = 1002; @@ -220,13 +228,13 @@ public class EditorTouchStateTest { long event1Time = 1000; MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f); mTouchState.update(event1, mConfig); - assertSingleTap(mTouchState, 20f, 30f, 0, 0, false); + assertSingleTap(mTouchState, 20f, 30f, 0, 0); // Simulate an ACTION_UP event. long event2Time = 1001; MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f); mTouchState.update(event2, mConfig); - assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false); + assertSingleTap(mTouchState, 20f, 30f, 20f, 30f); // Generate a second ACTION_DOWN event whose time is within the double-tap timeout. long event3Time = 1002; @@ -246,7 +254,7 @@ public class EditorTouchStateTest { long event5Time = 1004; MotionEvent event5 = downEvent(event5Time, event5Time, 22f, 32f); mTouchState.update(event5, mConfig); - assertSingleTap(mTouchState, 22f, 32f, 21f, 31f, false); + assertSingleTap(mTouchState, 22f, 32f, 21f, 31f); } @Test @@ -255,13 +263,13 @@ public class EditorTouchStateTest { long event1Time = 1000; MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f); mTouchState.update(event1, mConfig); - assertSingleTap(mTouchState, 20f, 30f, 0, 0, false); + assertSingleTap(mTouchState, 20f, 30f, 0, 0); // Simulate an ACTION_MOVE event whose location is not far enough to start a drag. long event2Time = 1001; MotionEvent event2 = moveEvent(event1Time, event2Time, 21f, 30f); mTouchState.update(event2, mConfig); - assertSingleTap(mTouchState, 20f, 30f, 0, 0, false); + assertSingleTap(mTouchState, 20f, 30f, 0, 0); // Simulate another ACTION_MOVE event whose location is far enough to start a drag. int touchSlop = mConfig.getScaledTouchSlop(); @@ -270,21 +278,135 @@ public class EditorTouchStateTest { long event3Time = 1002; MotionEvent event3 = moveEvent(event3Time, event3Time, newX, newY); mTouchState.update(event3, mConfig); - assertSingleTap(mTouchState, 20f, 30f, 0, 0, true); + assertDrag(mTouchState, 20f, 30f, 0, 0, false); // Simulate an ACTION_UP event. long event4Time = 1003; MotionEvent event4 = upEvent(event3Time, event4Time, 200f, 300f); mTouchState.update(event4, mConfig); - assertSingleTap(mTouchState, 20f, 30f, 200f, 300f, false); + assertSingleTap(mTouchState, 20f, 30f, 200f, 300f); } @Test - public void testIsDistanceWithin() throws Exception { - assertTrue(EditorTouchState.isDistanceWithin(0, 0, 0, 0, 8)); - assertTrue(EditorTouchState.isDistanceWithin(3, 9, 5, 11, 8)); - assertTrue(EditorTouchState.isDistanceWithin(5, 11, 3, 9, 8)); - assertFalse(EditorTouchState.isDistanceWithin(5, 10, 5, 20, 8)); + public void testUpdate_drag_startsCloseToVerticalThenHorizontal() throws Exception { + // Simulate an ACTION_DOWN event. + long event1Time = 1001; + MotionEvent event1 = downEvent(event1Time, event1Time, 0f, 0f); + mTouchState.update(event1, mConfig); + assertSingleTap(mTouchState, 0f, 0f, 0, 0); + + // Simulate an ACTION_MOVE event that is < 30 deg from vertical. + long event2Time = 1002; + MotionEvent event2 = moveEvent(event1Time, event2Time, 100f, 174f); + mTouchState.update(event2, mConfig); + assertDrag(mTouchState, 0f, 0f, 0, 0, true); + + // Simulate another ACTION_MOVE event that is horizontal from the original down event. + // The value of `isDragCloseToVertical` should NOT change since it should only reflect the + // initial direction of movement. + long event3Time = 1003; + MotionEvent event3 = moveEvent(event1Time, event3Time, 200f, 0f); + mTouchState.update(event3, mConfig); + assertDrag(mTouchState, 0f, 0f, 0, 0, true); + + // Simulate an ACTION_UP event. + long event4Time = 1004; + MotionEvent event4 = upEvent(event1Time, event4Time, 200f, 0f); + mTouchState.update(event4, mConfig); + assertSingleTap(mTouchState, 0f, 0f, 200f, 0f); + } + + @Test + public void testUpdate_drag_startsHorizontalThenVertical() throws Exception { + // Simulate an ACTION_DOWN event. + long event1Time = 1001; + MotionEvent event1 = downEvent(event1Time, event1Time, 0f, 0f); + mTouchState.update(event1, mConfig); + assertSingleTap(mTouchState, 0f, 0f, 0, 0); + + // Simulate an ACTION_MOVE event that is > 30 deg from vertical. + long event2Time = 1002; + MotionEvent event2 = moveEvent(event1Time, event2Time, 100f, 173f); + mTouchState.update(event2, mConfig); + assertDrag(mTouchState, 0f, 0f, 0, 0, false); + + // Simulate another ACTION_MOVE event that is vertical from the original down event. + // The value of `isDragCloseToVertical` should NOT change since it should only reflect the + // initial direction of movement. + long event3Time = 1003; + MotionEvent event3 = moveEvent(event1Time, event3Time, 0f, 200f); + mTouchState.update(event3, mConfig); + assertDrag(mTouchState, 0f, 0f, 0, 0, false); + + // Simulate an ACTION_UP event. + long event4Time = 1004; + MotionEvent event4 = upEvent(event1Time, event4Time, 0f, 200f); + mTouchState.update(event4, mConfig); + assertSingleTap(mTouchState, 0f, 0f, 0f, 200f); + } + + @Test + public void testUpdate_cancelAfterDown() throws Exception { + // Simulate an ACTION_DOWN event. + long event1Time = 1001; + MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f); + mTouchState.update(event1, mConfig); + assertSingleTap(mTouchState, 20f, 30f, 0, 0); + + // Simulate an ACTION_CANCEL event. + long event2Time = 1002; + MotionEvent event2 = cancelEvent(event1Time, event2Time, 20f, 30f); + mTouchState.update(event2, mConfig); + assertSingleTap(mTouchState, 20f, 30f, 0, 0); + } + + @Test + public void testUpdate_cancelAfterDrag() throws Exception { + // Simulate an ACTION_DOWN event. + long event1Time = 1001; + MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f); + mTouchState.update(event1, mConfig); + assertSingleTap(mTouchState, 20f, 30f, 0, 0); + + // Simulate another ACTION_MOVE event whose location is far enough to start a drag. + long event2Time = 1002; + MotionEvent event2 = moveEvent(event2Time, event2Time, 200f, 30f); + mTouchState.update(event2, mConfig); + assertDrag(mTouchState, 20f, 30f, 0, 0, false); + + // Simulate an ACTION_CANCEL event. + long event3Time = 1003; + MotionEvent event3 = cancelEvent(event1Time, event3Time, 200f, 30f); + mTouchState.update(event3, mConfig); + assertSingleTap(mTouchState, 20f, 30f, 0, 0); + } + + @Test + public void testUpdate_cancelAfterMultitap() throws Exception { + // Simulate an ACTION_DOWN event. + long event1Time = 1001; + MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f); + mTouchState.update(event1, mConfig); + assertSingleTap(mTouchState, 20f, 30f, 0, 0); + + // Simulate an ACTION_UP event. + long event2Time = 1002; + MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f); + mTouchState.update(event2, mConfig); + assertSingleTap(mTouchState, 20f, 30f, 20f, 30f); + + // Generate an ACTION_DOWN event whose time is within the double-tap timeout. + long event3Time = 1003; + MotionEvent event3 = downEvent(event3Time, event3Time, 22f, 33f); + mTouchState.update(event3, mConfig); + assertMultiTap(mTouchState, 22f, 33f, 20f, 30f, + MultiTapStatus.DOUBLE_TAP, true); + + // Simulate an ACTION_CANCEL event. + long event4Time = 1004; + MotionEvent event4 = cancelEvent(event3Time, event4Time, 20f, 30f); + mTouchState.update(event4, mConfig); + assertSingleTap(mTouchState, 22f, 33f, 20f, 30f); } private static MotionEvent downEvent(long downTime, long eventTime, float x, float y) { @@ -299,8 +421,25 @@ public class EditorTouchStateTest { return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0); } + private static MotionEvent cancelEvent(long downTime, long eventTime, float x, float y) { + return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_CANCEL, x, y, 0); + } + private static void assertSingleTap(EditorTouchState touchState, float lastDownX, - float lastDownY, float lastUpX, float lastUpY, boolean isMovedEnoughForDrag) { + float lastDownY, float lastUpX, float lastUpY) { + assertThat(touchState.getLastDownX(), is(lastDownX)); + assertThat(touchState.getLastDownY(), is(lastDownY)); + assertThat(touchState.getLastUpX(), is(lastUpX)); + assertThat(touchState.getLastUpY(), is(lastUpY)); + assertThat(touchState.isDoubleTap(), is(false)); + assertThat(touchState.isTripleClick(), is(false)); + assertThat(touchState.isMultiTap(), is(false)); + assertThat(touchState.isMultiTapInSameArea(), is(false)); + assertThat(touchState.isMovedEnoughForDrag(), is(false)); + } + + private static void assertDrag(EditorTouchState touchState, float lastDownX, + float lastDownY, float lastUpX, float lastUpY, boolean isDragCloseToVertical) { assertThat(touchState.getLastDownX(), is(lastDownX)); assertThat(touchState.getLastDownY(), is(lastDownY)); assertThat(touchState.getLastUpX(), is(lastUpX)); @@ -309,7 +448,8 @@ public class EditorTouchStateTest { assertThat(touchState.isTripleClick(), is(false)); assertThat(touchState.isMultiTap(), is(false)); assertThat(touchState.isMultiTapInSameArea(), is(false)); - assertThat(touchState.isMovedEnoughForDrag(), is(isMovedEnoughForDrag)); + assertThat(touchState.isMovedEnoughForDrag(), is(true)); + assertThat(touchState.isDragCloseToVertical(), is(isDragCloseToVertical)); } private static void assertMultiTap(EditorTouchState touchState, @@ -325,5 +465,6 @@ public class EditorTouchStateTest { || multiTapStatus == MultiTapStatus.TRIPLE_CLICK)); assertThat(touchState.isMultiTapInSameArea(), is(isMultiTapInSameArea)); assertThat(touchState.isMovedEnoughForDrag(), is(false)); + assertThat(touchState.isDragCloseToVertical(), is(false)); } } diff --git a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java index b4116681e723..a0cfb31e390f 100644 --- a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java +++ b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java @@ -48,7 +48,6 @@ import android.view.textclassifier.TextClassificationManager; import android.view.textclassifier.TextClassifier; import androidx.test.filters.MediumTest; -import androidx.test.filters.Suppress; import androidx.test.rule.ActivityTestRule; import androidx.test.runner.AndroidJUnit4; @@ -64,7 +63,6 @@ import org.junit.runner.RunWith; */ @RunWith(AndroidJUnit4.class) @MediumTest -@Suppress // Consistently failing. b/29591177 public class TextViewActivityMouseTest { @Rule @@ -86,22 +84,12 @@ public class TextViewActivityMouseTest { onView(withId(R.id.textview)).perform(mouseClick()); onView(withId(R.id.textview)).perform(replaceText(helloWorld)); - assertNoSelectionHandles(); - onView(withId(R.id.textview)).perform( mouseDragOnText(helloWorld.indexOf("llo"), helloWorld.indexOf("ld!"))); - onView(withId(R.id.textview)).check(hasSelection("llo wor")); - onHandleView(com.android.internal.R.id.selection_start_handle) - .check(matches(isDisplayed())); - onHandleView(com.android.internal.R.id.selection_end_handle) - .check(matches(isDisplayed())); - onView(withId(R.id.textview)).perform(mouseClickOnTextAtIndex(helloWorld.indexOf("w"))); onView(withId(R.id.textview)).check(hasSelection("")); - - assertNoSelectionHandles(); } @Test @@ -196,7 +184,6 @@ public class TextViewActivityMouseTest { onView(withId(R.id.textview)).check(matches(withText("abc ghi.def"))); onView(withId(R.id.textview)).check(hasSelection("")); - assertNoSelectionHandles(); onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex("abc ghi.def".length())); } @@ -213,7 +200,6 @@ public class TextViewActivityMouseTest { onView(withId(R.id.textview)).check(matches(withText("abc ghi.def"))); onView(withId(R.id.textview)).check(hasSelection("")); - assertNoSelectionHandles(); onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex("abc ghi.def".length())); } @@ -403,4 +389,29 @@ public class TextViewActivityMouseTest { mouseTripleClickAndDragOnText(text.indexOf("ird"), text.indexOf("First"))); onView(withId(R.id.textview)).check(hasSelection(text)); } + + @Test + public void testSelectionHandlesDisplay() { + final String helloWorld = "Hello world!"; + onView(withId(R.id.textview)).perform(mouseClick()); + onView(withId(R.id.textview)).perform(replaceText(helloWorld)); + + onView(withId(R.id.textview)).perform( + mouseDragOnText(helloWorld.indexOf("llo"), helloWorld.indexOf("ld!"))); + onView(withId(R.id.textview)).check(hasSelection("llo wor")); + + // Confirm that selection handles are shown when there is a selection. + onHandleView(com.android.internal.R.id.selection_start_handle) + .check(matches(isDisplayed())); + onHandleView(com.android.internal.R.id.selection_end_handle) + .check(matches(isDisplayed())); + + onView(withId(R.id.textview)).perform(mouseClickOnTextAtIndex(helloWorld.indexOf("w"))); + onView(withId(R.id.textview)).check(hasSelection("")); + + // Confirm that the selection handles are not shown when there is no selection. This + // assertion is slow so we only do it in this one test case. The rest of the tests just + // assert via `hasSelection("")`. + assertNoSelectionHandles(); + } } diff --git a/core/tests/coretests/src/android/widget/espresso/MouseUiController.java b/core/tests/coretests/src/android/widget/espresso/MouseUiController.java index abee7369414b..1928d2587f70 100644 --- a/core/tests/coretests/src/android/widget/espresso/MouseUiController.java +++ b/core/tests/coretests/src/android/widget/espresso/MouseUiController.java @@ -70,6 +70,8 @@ public final class MouseUiController implements UiController { event.setSource(InputDevice.SOURCE_MOUSE); if (event.getActionMasked() != MotionEvent.ACTION_UP) { event.setButtonState(mButton); + } else { + event.setButtonState(0); } return mUiController.injectMotionEvent(event); } diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java index 82854e5b8a9d..6784ede5b5da 100644 --- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java +++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java @@ -348,7 +348,7 @@ public class AccessibilityShortcutControllerTest { verify(mAlertDialog).show(); verify(mAccessibilityManagerService, atLeastOnce()).getInstalledAccessibilityServiceList( anyInt()); - verify(mAccessibilityManagerService, times(0)).performAccessibilityShortcut(); + verify(mAccessibilityManagerService, times(0)).performAccessibilityShortcut(null); verify(mFrameworkObjectProvider, times(0)).getTextToSpeech(any(), any()); } @@ -365,7 +365,7 @@ public class AccessibilityShortcutControllerTest { assertEquals(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS, mLayoutParams.privateFlags & WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS); - verify(mAccessibilityManagerService, times(1)).performAccessibilityShortcut(); + verify(mAccessibilityManagerService, times(1)).performAccessibilityShortcut(null); } @Test @@ -433,7 +433,7 @@ public class AccessibilityShortcutControllerTest { verifyZeroInteractions(mAlertDialogBuilder, mAlertDialog); verify(mToast).show(); - verify(mAccessibilityManagerService).performAccessibilityShortcut(); + verify(mAccessibilityManagerService).performAccessibilityShortcut(null); } @Test @@ -459,7 +459,7 @@ public class AccessibilityShortcutControllerTest { when(mServiceInfo.loadSummary(any())).thenReturn(null); Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1); getController().performAccessibilityShortcut(); - verify(mAccessibilityManagerService).performAccessibilityShortcut(); + verify(mAccessibilityManagerService).performAccessibilityShortcut(null); } @Test @@ -471,7 +471,7 @@ public class AccessibilityShortcutControllerTest { getController().performAccessibilityShortcut(); verifyZeroInteractions(mToast); - verify(mAccessibilityManagerService).performAccessibilityShortcut(); + verify(mAccessibilityManagerService).performAccessibilityShortcut(null); } @Test @@ -485,7 +485,7 @@ public class AccessibilityShortcutControllerTest { getController().performAccessibilityShortcut(); verifyZeroInteractions(mToast); - verify(mAccessibilityManagerService).performAccessibilityShortcut(); + verify(mAccessibilityManagerService).performAccessibilityShortcut(null); } @Test diff --git a/core/tests/overlaytests/remount/host/AndroidTest.xml b/core/tests/overlaytests/remount/host/AndroidTest.xml index 11eadf1a4659..087b7313693d 100644 --- a/core/tests/overlaytests/remount/host/AndroidTest.xml +++ b/core/tests/overlaytests/remount/host/AndroidTest.xml @@ -19,9 +19,6 @@ <option name="test-tag" value="OverlayRemountedTest" /> <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/> - <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> - <option name="run-command" value="remount" /> - </target_preparer> <test class="com.android.tradefed.testtype.HostTest"> <option name="jar" value="OverlayRemountedTest.jar" /> diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlayHostTest.java b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlayHostTest.java deleted file mode 100644 index 84af18710fe6..000000000000 --- a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlayHostTest.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.overlaytest.remounted; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import com.android.tradefed.device.DeviceNotAvailableException; -import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; - -import org.junit.Rule; -import org.junit.rules.RuleChain; -import org.junit.rules.TemporaryFolder; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class OverlayHostTest extends BaseHostJUnit4Test { - private static final long TIME_OUT_MS = 30000; - private static final String RES_INSTRUMENTATION_ARG = "res"; - private static final String OVERLAY_INSTRUMENTATION_ARG = "overlays"; - private static final String RESOURCES_TYPE_SUFFIX = "_type"; - private static final String RESOURCES_DATA_SUFFIX = "_data"; - - public final TemporaryFolder mTemporaryFolder = new TemporaryFolder(); - public final SystemPreparer mPreparer = new SystemPreparer(mTemporaryFolder, this::getDevice); - - @Rule - public final RuleChain ruleChain = RuleChain.outerRule(mTemporaryFolder).around(mPreparer); - private Map<String, String> mLastResults; - - /** - * Retrieves the values of the resources in the test package. The test package must use the - * {@link com.android.overlaytest.remounted.target.ResourceRetrievalRunner} instrumentation. - **/ - void retrieveResource(String testPackageName, List<String> requiredOverlayPaths, - String... resourceNames) throws DeviceNotAvailableException { - final HashMap<String, String> args = new HashMap<>(); - if (!requiredOverlayPaths.isEmpty()) { - // Enclose the require overlay paths in quotes so the arguments will be string arguments - // rather than file arguments. - args.put(OVERLAY_INSTRUMENTATION_ARG, - String.format("\"%s\"", String.join(" ", requiredOverlayPaths))); - } - - if (resourceNames.length == 0) { - throw new IllegalArgumentException("Must specify at least one resource to retrieve."); - } - - // Pass the names of the resources to retrieve into the test as one string. - args.put(RES_INSTRUMENTATION_ARG, - String.format("\"%s\"", String.join(" ", resourceNames))); - - runDeviceTests(getDevice(), null, testPackageName, null, null, null, TIME_OUT_MS, - TIME_OUT_MS, TIME_OUT_MS, false, false, args); - - // Retrieve the results of the most recently run test. - mLastResults = (getLastDeviceRunResults().getRunMetrics() == mLastResults) ? null : - getLastDeviceRunResults().getRunMetrics(); - } - - /** Returns the base resource directories of the specified packages. */ - List<String> getPackagePaths(String... packageNames) - throws DeviceNotAvailableException { - final ArrayList<String> paths = new ArrayList<>(); - for (String packageName : packageNames) { - // Use the package manager shell command to find the path of the package. - final String result = getDevice().executeShellCommand( - String.format("pm dump %s | grep \"resourcePath=\"", packageName)); - assertNotNull("Failed to find path for package " + packageName, result); - int splitIndex = result.indexOf('='); - assertTrue(splitIndex >= 0); - paths.add(result.substring(splitIndex + 1).trim()); - } - return paths; - } - - /** Builds the full name of a resource in the form package:type/entry. */ - String resourceName(String pkg, String type, String entry) { - return String.format("%s:%s/%s", pkg, type, entry); - } - - /** - * Asserts that the type and data of a a previously retrieved is the same as expected. - * @param resourceName the full name of the resource in the form package:type/entry - * @param type the expected {@link android.util.TypedValue} type of the resource - * @param data the expected value of the resource when coerced to a string using - * {@link android.util.TypedValue#coerceToString()} - **/ - void assertResource(String resourceName, int type, String data) { - assertNotNull("Failed to get test results", mLastResults); - assertNotEquals("No resource values were retrieved", mLastResults.size(), 0); - assertEquals("" + type, mLastResults.get(resourceName + RESOURCES_TYPE_SUFFIX)); - assertEquals("" + data, mLastResults.get(resourceName + RESOURCES_DATA_SUFFIX)); - } -} diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java index 4939e160612e..06b2ac8f9e22 100644 --- a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java +++ b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java @@ -16,17 +16,21 @@ package com.android.overlaytest.remounted; +import static org.junit.Assert.assertTrue; + import com.android.tradefed.device.DeviceNotAvailableException; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; +import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.RuleChain; +import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; -import java.util.Collections; -import java.util.List; - @RunWith(DeviceJUnit4ClassRunner.class) -public class OverlaySharedLibraryTest extends OverlayHostTest { +public class OverlaySharedLibraryTest extends BaseHostJUnit4Test { private static final String TARGET_APK = "OverlayRemountedTest_Target.apk"; private static final String TARGET_PACKAGE = "com.android.overlaytest.remounted.target"; private static final String SHARED_LIBRARY_APK = @@ -38,6 +42,17 @@ public class OverlaySharedLibraryTest extends OverlayHostTest { private static final String SHARED_LIBRARY_OVERLAY_PACKAGE = "com.android.overlaytest.remounted.shared_library.overlay"; + public final TemporaryFolder temporaryFolder = new TemporaryFolder(); + public final SystemPreparer preparer = new SystemPreparer(temporaryFolder, this::getDevice); + + @Rule + public final RuleChain ruleChain = RuleChain.outerRule(temporaryFolder).around(preparer); + + @Before + public void startBefore() throws DeviceNotAvailableException { + getDevice().waitForDeviceAvailable(); + } + @Test public void testSharedLibrary() throws Exception { final String targetResource = resourceName(TARGET_PACKAGE, "bool", @@ -45,23 +60,20 @@ public class OverlaySharedLibraryTest extends OverlayHostTest { final String libraryResource = resourceName(SHARED_LIBRARY_PACKAGE, "bool", "shared_library_overlaid"); - mPreparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk") + preparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk") .installResourceApk(SHARED_LIBRARY_OVERLAY_APK, SHARED_LIBRARY_OVERLAY_PACKAGE) .reboot() .setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, false) .installResourceApk(TARGET_APK, TARGET_PACKAGE); // The shared library resource is not currently overlaid. - retrieveResource(Collections.emptyList(), targetResource, libraryResource); - assertResource(targetResource, 0x12 /* TYPE_INT_BOOLEAN */, "false"); - assertResource(libraryResource, 0x12 /* TYPE_INT_BOOLEAN */, "false"); + assertResource(targetResource, "false"); + assertResource(libraryResource, "false"); // Overlay the shared library resource. - mPreparer.setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true); - retrieveResource(getPackagePaths(SHARED_LIBRARY_OVERLAY_PACKAGE), targetResource, - libraryResource); - assertResource(targetResource, 0x12 /* TYPE_INT_BOOLEAN */, "true"); - assertResource(libraryResource, 0x12 /* TYPE_INT_BOOLEAN */, "true"); + preparer.setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true); + assertResource(targetResource, "true"); + assertResource(libraryResource, "true"); } @Test @@ -71,20 +83,27 @@ public class OverlaySharedLibraryTest extends OverlayHostTest { final String libraryResource = resourceName(SHARED_LIBRARY_PACKAGE, "bool", "shared_library_overlaid"); - mPreparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk") + preparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk") .installResourceApk(SHARED_LIBRARY_OVERLAY_APK, SHARED_LIBRARY_OVERLAY_PACKAGE) .setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true) .reboot() .installResourceApk(TARGET_APK, TARGET_PACKAGE); - retrieveResource(getPackagePaths(SHARED_LIBRARY_OVERLAY_PACKAGE), targetResource, - libraryResource); - assertResource(targetResource, 0x12 /* TYPE_INT_BOOLEAN */, "true"); - assertResource(libraryResource, 0x12 /* TYPE_INT_BOOLEAN */, "true"); + assertResource(targetResource, "true"); + assertResource(libraryResource, "true"); + } + + /** Builds the full name of a resource in the form package:type/entry. */ + String resourceName(String pkg, String type, String entry) { + return String.format("%s:%s/%s", pkg, type, entry); } - private void retrieveResource(List<String> requiredOverlayPaths, String... resourceNames) + void assertResource(String resourceName, String expectedValue) throws DeviceNotAvailableException { - retrieveResource(TARGET_PACKAGE, requiredOverlayPaths, resourceNames); + final String result = getDevice().executeShellCommand( + String.format("cmd overlay lookup %s %s", TARGET_PACKAGE, resourceName)); + assertTrue(String.format("expected: <[%s]> in: <[%s]>", expectedValue, result), + result.equals(expectedValue + "\n") || + result.endsWith("-> " + expectedValue + "\n")); } } diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java index 7028f2f1d554..8696091239c2 100644 --- a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java +++ b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java @@ -38,8 +38,7 @@ import java.util.concurrent.FutureTask; import java.util.concurrent.TimeoutException; class SystemPreparer extends ExternalResource { - private static final long REBOOT_SLEEP_MS = 30000; - private static final long OVERLAY_ENABLE_TIMEOUT_MS = 20000; + private static final long OVERLAY_ENABLE_TIMEOUT_MS = 30000; // The paths of the files pushed onto the device through this rule. private ArrayList<String> mPushedFiles = new ArrayList<>(); @@ -59,6 +58,7 @@ class SystemPreparer extends ExternalResource { SystemPreparer pushResourceFile(String resourcePath, String outputPath) throws DeviceNotAvailableException, IOException { final ITestDevice device = mDeviceProvider.getDevice(); + device.executeAdbCommand("remount"); assertTrue(device.pushFile(copyResourceToTemp(resourcePath), outputPath)); mPushedFiles.add(outputPath); return this; @@ -77,7 +77,7 @@ class SystemPreparer extends ExternalResource { /** Sets the enable state of an overlay pacakage. */ SystemPreparer setOverlayEnabled(String packageName, boolean enabled) - throws ExecutionException, TimeoutException { + throws ExecutionException, DeviceNotAvailableException { final ITestDevice device = mDeviceProvider.getDevice(); // Wait for the overlay to change its enabled state. @@ -86,8 +86,10 @@ class SystemPreparer extends ExternalResource { device.executeShellCommand(String.format("cmd overlay %s %s", enabled ? "enable" : "disable", packageName)); - final String pattern = (enabled ? "[x]" : "[ ]") + " " + packageName; - if (device.executeShellCommand("cmd overlay list").contains(pattern)) { + final String result = device.executeShellCommand("cmd overlay dump " + packageName); + final int startIndex = result.indexOf("mIsEnabled"); + final int endIndex = result.indexOf('\n', startIndex); + if (result.substring(startIndex, endIndex).contains((enabled) ? "true" : "false")) { return true; } } @@ -98,6 +100,8 @@ class SystemPreparer extends ExternalResource { try { enabledListener.get(OVERLAY_ENABLE_TIMEOUT_MS, MILLISECONDS); } catch (InterruptedException ignored) { + } catch (TimeoutException e) { + throw new IllegalStateException(device.executeShellCommand("cmd overlay list")); } return this; @@ -106,14 +110,7 @@ class SystemPreparer extends ExternalResource { /** Restarts the device and waits until after boot is completed. */ SystemPreparer reboot() throws DeviceNotAvailableException { final ITestDevice device = mDeviceProvider.getDevice(); - device.executeShellCommand("stop"); - device.executeShellCommand("start"); - try { - // Sleep until the device is ready for test execution. - Thread.sleep(REBOOT_SLEEP_MS); - } catch (InterruptedException ignored) { - } - + device.reboot(); return this; } @@ -141,12 +138,14 @@ class SystemPreparer extends ExternalResource { protected void after() { final ITestDevice device = mDeviceProvider.getDevice(); try { + device.executeAdbCommand("remount"); for (final String file : mPushedFiles) { device.deleteFile(file); } for (final String packageName : mInstalledPackages) { device.uninstallPackage(packageName); } + device.reboot(); } catch (DeviceNotAvailableException e) { Assert.fail(e.toString()); } diff --git a/core/tests/overlaytests/remount/target/AndroidManifest.xml b/core/tests/overlaytests/remount/target/AndroidManifest.xml index 32fec43593f8..dc07dca16718 100644 --- a/core/tests/overlaytests/remount/target/AndroidManifest.xml +++ b/core/tests/overlaytests/remount/target/AndroidManifest.xml @@ -23,8 +23,4 @@ <uses-library android:name="com.android.overlaytest.remounted.shared_library" android:required="true" /> </application> - - <instrumentation android:name="com.android.overlaytest.remounted.target.ResourceRetrievalRunner" - android:targetPackage="com.android.overlaytest.remounted.target" - android:label="Remounted system RRO tests" /> </manifest> diff --git a/core/tests/overlaytests/remount/target/src/com/android/overlaytest/remounted/target/ResourceRetrievalRunner.java b/core/tests/overlaytests/remount/target/src/com/android/overlaytest/remounted/target/ResourceRetrievalRunner.java deleted file mode 100644 index 2e4c211d6a0a..000000000000 --- a/core/tests/overlaytests/remount/target/src/com/android/overlaytest/remounted/target/ResourceRetrievalRunner.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.overlaytest.remounted.target; - -import android.app.Activity; -import android.app.Instrumentation; -import android.content.res.Resources; -import android.os.Bundle; -import android.util.Log; -import android.util.TypedValue; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.concurrent.Executor; -import java.util.concurrent.FutureTask; -import java.util.concurrent.TimeUnit; - -/** - * An {@link Instrumentation} that retrieves the value of specified resources within the - * application. - **/ -public class ResourceRetrievalRunner extends Instrumentation { - private static final String TAG = ResourceRetrievalRunner.class.getSimpleName(); - - // A list of whitespace separated resource names of which to retrieve the resource values. - private static final String RESOURCE_LIST_TAG = "res"; - - // A list of whitespace separated overlay package paths that must be present before retrieving - // resource values. - private static final String REQUIRED_OVERLAYS_LIST_TAG = "overlays"; - - // The suffixes of the keys returned from the instrumentation. To retrieve the type of a - // resource looked up with the instrumentation, append the {@link #RESOURCES_TYPE_SUFFIX} suffix - // to the end of the name of the resource. For the value of a resource, use - // {@link #RESOURCES_DATA_SUFFIX} instead. - private static final String RESOURCES_TYPE_SUFFIX = "_type"; - private static final String RESOURCES_DATA_SUFFIX = "_data"; - - // The amount of time in seconds to wait for the overlays to be present in the AssetManager. - private static final int OVERLAY_PATH_TIMEOUT = 60; - - private final ArrayList<String> mResourceNames = new ArrayList<>(); - private final ArrayList<String> mOverlayPaths = new ArrayList<>(); - private final Bundle mResult = new Bundle(); - - /** - * Receives the instrumentation arguments and runs the resource retrieval. - * The entry with key {@link #RESOURCE_LIST_TAG} in the {@link Bundle} arguments is a - * whitespace separated string of resource names of which to retrieve the resource values. - * The entry with key {@link #REQUIRED_OVERLAYS_LIST_TAG} in the {@link Bundle} arguments is a - * whitespace separated string of overlay package paths prefixes that must be present before - * retrieving the resource values. - */ - @Override - public void onCreate(Bundle arguments) { - super.onCreate(arguments); - mResourceNames.addAll(Arrays.asList(arguments.getString(RESOURCE_LIST_TAG).split(" "))); - if (arguments.containsKey(REQUIRED_OVERLAYS_LIST_TAG)) { - mOverlayPaths.addAll(Arrays.asList( - arguments.getString(REQUIRED_OVERLAYS_LIST_TAG).split(" "))); - } - start(); - } - - @Override - public void onStart() { - final Resources res = getContext().getResources(); - res.getAssets().setResourceResolutionLoggingEnabled(true); - - if (!mOverlayPaths.isEmpty()) { - Log.d(TAG, String.format("Waiting for overlay paths [%s]", - String.join(",", mOverlayPaths))); - - // Wait for all required overlays to be present in the AssetManager. - final FutureTask<Boolean> overlayListener = new FutureTask<>(() -> { - while (!mOverlayPaths.isEmpty()) { - final String[] apkPaths = res.getAssets().getApkPaths(); - for (String path : apkPaths) { - for (String overlayPath : mOverlayPaths) { - if (path.startsWith(overlayPath)) { - mOverlayPaths.remove(overlayPath); - break; - } - } - } - } - return true; - }); - - try { - final Executor executor = (t) -> new Thread(t).start(); - executor.execute(overlayListener); - overlayListener.get(OVERLAY_PATH_TIMEOUT, TimeUnit.SECONDS); - } catch (Exception e) { - Log.e(TAG, String.format("Failed to wait for required overlays [%s]", - String.join(",", mOverlayPaths)), e); - finish(Activity.RESULT_CANCELED, mResult); - } - } - - // Retrieve the values for each resource passed in. - final TypedValue typedValue = new TypedValue(); - for (final String resourceName : mResourceNames) { - try { - final int resId = res.getIdentifier(resourceName, null, null); - res.getValue(resId, typedValue, true); - Log.d(TAG, String.format("Resolution for 0x%s: %s", Integer.toHexString(resId), - res.getAssets().getLastResourceResolution())); - } catch (Resources.NotFoundException e) { - Log.e(TAG, "Failed to retrieve value for resource " + resourceName, e); - finish(Activity.RESULT_CANCELED, mResult); - } - - putValue(resourceName, typedValue); - } - - finish(Activity.RESULT_OK, mResult); - } - - private void putValue(String resourceName, TypedValue value) { - mResult.putInt(resourceName + RESOURCES_TYPE_SUFFIX, value.type); - final CharSequence textValue = value.coerceToString(); - mResult.putString(resourceName + RESOURCES_DATA_SUFFIX, - textValue == null ? "null" : textValue.toString()); - } -} diff --git a/data/etc/car/Android.bp b/data/etc/car/Android.bp index 26a40d3dea8c..dfb7a16e6771 100644 --- a/data/etc/car/Android.bp +++ b/data/etc/car/Android.bp @@ -135,3 +135,10 @@ prebuilt_etc { src: "com.android.car.floatingcardslauncher.xml", filename_from_src: true, } + +prebuilt_etc { + name: "privapp_whitelist_com.android.car.ui.paintbooth", + sub_dir: "permissions", + src: "com.android.car.ui.paintbooth.xml", + filename_from_src: true, +} diff --git a/data/etc/car/com.android.car.developeroptions.xml b/data/etc/car/com.android.car.developeroptions.xml index 5f5e908322bc..720489834494 100644 --- a/data/etc/car/com.android.car.developeroptions.xml +++ b/data/etc/car/com.android.car.developeroptions.xml @@ -40,6 +40,7 @@ <permission name="android.permission.MOVE_PACKAGE"/> <permission name="android.permission.OVERRIDE_WIFI_CONFIG"/> <permission name="android.permission.PACKAGE_USAGE_STATS"/> + <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/> <permission name="android.permission.READ_SEARCH_INDEXABLES"/> <permission name="android.permission.REBOOT"/> <permission name="android.permission.REQUEST_NETWORK_SCORES"/> diff --git a/data/etc/car/com.android.car.ui.paintbooth.xml b/data/etc/car/com.android.car.ui.paintbooth.xml new file mode 100644 index 000000000000..11bf304fe8f1 --- /dev/null +++ b/data/etc/car/com.android.car.ui.paintbooth.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<permissions> + + <privapp-permissions package="com.android.car.ui.paintbooth"> + <!-- For enabling/disabling, and getting list of RROs --> + <permission name="android.permission.CHANGE_OVERLAY_PACKAGES"/> + <!-- For showing the current active activity --> + <permission name="android.permission.REAL_GET_TASKS"/> + <!-- For getting list of RROs for current user --> + <permission name="android.permission.MANAGE_USERS"/> + <!-- For getting list of RROs for current user--> + <permission name="android.permission.INTERACT_ACROSS_USERS"/> + </privapp-permissions> +</permissions> diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml index 1d735af524a4..40de83aefb18 100644 --- a/data/etc/com.android.systemui.xml +++ b/data/etc/com.android.systemui.xml @@ -22,7 +22,6 @@ <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/> <permission name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"/> <permission name="android.permission.CHANGE_OVERLAY_PACKAGES"/> - <permission name="android.permission.CONNECTIVITY_INTERNAL"/> <permission name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS"/> <permission name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"/> <permission name="android.permission.CONTROL_VPN"/> @@ -38,6 +37,7 @@ <permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/> <permission name="android.permission.MODIFY_PHONE_STATE"/> <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> + <permission name="android.permission.OBSERVE_NETWORK_POLICY"/> <permission name="android.permission.OVERRIDE_WIFI_CONFIG"/> <permission name="android.permission.READ_DREAM_STATE"/> <permission name="android.permission.READ_FRAME_BUFFER"/> diff --git a/data/etc/platform.xml b/data/etc/platform.xml index 0574775712a6..877ef2687349 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -180,7 +180,6 @@ <assign-permission name="android.permission.ACCESS_LOWPAN_STATE" uid="lowpan" /> <assign-permission name="android.permission.MANAGE_LOWPAN_INTERFACES" uid="lowpan" /> - <assign-permission name="android.permission.BATTERY_STATS" uid="statsd" /> <assign-permission name="android.permission.DUMP" uid="statsd" /> <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="statsd" /> <assign-permission name="android.permission.STATSCOMPANION" uid="statsd" /> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 9930ea262b32..ad99ab335145 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -245,6 +245,7 @@ applications that come with the platform <permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/> <permission name="android.permission.TETHER_PRIVILEGED"/> <permission name="android.permission.UPDATE_APP_OPS_STATS"/> + <permission name="android.permission.UPDATE_DEVICE_STATS"/> </privapp-permissions> <privapp-permissions package="com.android.server.telecom"> diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java index ae90995573dc..447f043392c2 100644 --- a/graphics/java/android/graphics/FontFamily.java +++ b/graphics/java/android/graphics/FontFamily.java @@ -20,6 +20,7 @@ import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.res.AssetManager; import android.graphics.fonts.FontVariationAxis; +import android.os.Build; import android.text.TextUtils; import dalvik.annotation.optimization.CriticalNative; @@ -58,7 +59,8 @@ public class FontFamily { * * This cannot be deleted because it's in use by AndroidX. */ - @UnsupportedAppUsage(trackingBug = 123768928) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, + publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.") public long mNativePtr; // Points native font family builder. Must be zero after freezing this family. @@ -67,7 +69,8 @@ public class FontFamily { /** * This cannot be deleted because it's in use by AndroidX. */ - @UnsupportedAppUsage(trackingBug = 123768928) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, + publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.") public FontFamily() { mBuilderPtr = nInitBuilder(null, 0); mNativeBuilderCleaner = sBuilderRegistry.registerNativeAllocation(this, mBuilderPtr); @@ -76,7 +79,8 @@ public class FontFamily { /** * This cannot be deleted because it's in use by AndroidX. */ - @UnsupportedAppUsage(trackingBug = 123768928) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, + publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.") public FontFamily(@Nullable String[] langs, int variant) { final String langsString; if (langs == null || langs.length == 0) { @@ -98,7 +102,8 @@ public class FontFamily { * * This cannot be deleted because it's in use by AndroidX. */ - @UnsupportedAppUsage(trackingBug = 123768928) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, + publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.") public boolean freeze() { if (mBuilderPtr == 0) { throw new IllegalStateException("This FontFamily is already frozen"); @@ -115,7 +120,8 @@ public class FontFamily { /** * This cannot be deleted because it's in use by AndroidX. */ - @UnsupportedAppUsage(trackingBug = 123768928) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, + publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.") public void abortCreation() { if (mBuilderPtr == 0) { throw new IllegalStateException("This FontFamily is already frozen or abandoned"); @@ -127,7 +133,8 @@ public class FontFamily { /** * This cannot be deleted because it's in use by AndroidX. */ - @UnsupportedAppUsage(trackingBug = 123768928) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, + publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.") public boolean addFont(String path, int ttcIndex, FontVariationAxis[] axes, int weight, int italic) { if (mBuilderPtr == 0) { @@ -151,7 +158,8 @@ public class FontFamily { /** * This cannot be deleted because it's in use by AndroidX. */ - @UnsupportedAppUsage(trackingBug = 123768928) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, + publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.") public boolean addFontFromBuffer(ByteBuffer font, int ttcIndex, FontVariationAxis[] axes, int weight, int italic) { if (mBuilderPtr == 0) { @@ -179,7 +187,8 @@ public class FontFamily { * * This cannot be deleted because it's in use by AndroidX. */ - @UnsupportedAppUsage(trackingBug = 123768928) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, + publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.") public boolean addFontFromAssetManager(AssetManager mgr, String path, int cookie, boolean isAsset, int ttcIndex, int weight, int isItalic, FontVariationAxis[] axes) { diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java index 17aacb9756aa..fedde422e0be 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java @@ -308,6 +308,9 @@ public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { if (spec.isStrongBoxBacked()) { flags |= KeyStore.FLAG_STRONGBOX; } + if (spec.isCriticalToDeviceEncryption()) { + flags |= KeyStore.FLAG_CRITICAL_TO_DEVICE_ENCRYPTION; + } String keyAliasInKeystore = Credentials.USER_PRIVATE_KEY + spec.getKeystoreAlias(); KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics(); boolean success = false; diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java index 91aac8367976..c52fd48459cb 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -18,10 +18,8 @@ package android.security.keystore; import android.annotation.Nullable; import android.security.Credentials; -import android.security.GateKeeper; import android.security.KeyPairGeneratorSpec; import android.security.KeyStore; -import android.security.KeyStoreException; import android.security.keymaster.KeyCharacteristics; import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterCertificateChain; @@ -458,6 +456,9 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato if (mSpec.isStrongBoxBacked()) { flags |= KeyStore.FLAG_STRONGBOX; } + if (mSpec.isCriticalToDeviceEncryption()) { + flags |= KeyStore.FLAG_CRITICAL_TO_DEVICE_ENCRYPTION; + } byte[] additionalEntropy = KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index 52ff9e0449ca..450dd3301253 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -271,6 +271,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu private final boolean mIsStrongBoxBacked; private final boolean mUserConfirmationRequired; private final boolean mUnlockedDeviceRequired; + private final boolean mCriticalToDeviceEncryption; /* * ***NOTE***: All new fields MUST also be added to the following: * ParcelableKeyGenParameterSpec class. @@ -307,7 +308,8 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu boolean invalidatedByBiometricEnrollment, boolean isStrongBoxBacked, boolean userConfirmationRequired, - boolean unlockedDeviceRequired) { + boolean unlockedDeviceRequired, + boolean criticalToDeviceEncryption) { if (TextUtils.isEmpty(keyStoreAlias)) { throw new IllegalArgumentException("keyStoreAlias must not be empty"); } @@ -357,6 +359,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mIsStrongBoxBacked = isStrongBoxBacked; mUserConfirmationRequired = userConfirmationRequired; mUnlockedDeviceRequired = unlockedDeviceRequired; + mCriticalToDeviceEncryption = criticalToDeviceEncryption; } /** @@ -710,6 +713,16 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** + * Return whether this key is critical to the device encryption flow. + * + * @see android.security.KeyStore#FLAG_CRITICAL_TO_DEVICE_ENCRYPTION + * @hide + */ + public boolean isCriticalToDeviceEncryption() { + return mCriticalToDeviceEncryption; + } + + /** * Builder of {@link KeyGenParameterSpec} instances. */ public final static class Builder { @@ -741,6 +754,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu private boolean mIsStrongBoxBacked = false; private boolean mUserConfirmationRequired; private boolean mUnlockedDeviceRequired = false; + private boolean mCriticalToDeviceEncryption = false; /** * Creates a new instance of the {@code Builder}. @@ -804,6 +818,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mIsStrongBoxBacked = sourceSpec.isStrongBoxBacked(); mUserConfirmationRequired = sourceSpec.isUserConfirmationRequired(); mUnlockedDeviceRequired = sourceSpec.isUnlockedDeviceRequired(); + mCriticalToDeviceEncryption = sourceSpec.isCriticalToDeviceEncryption(); } /** @@ -1339,6 +1354,20 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** + * Set whether this key is critical to the device encryption flow + * + * This is a special flag only available to system servers to indicate the current key + * is part of the device encryption flow. + * + * @see android.security.KeyStore#FLAG_CRITICAL_TO_DEVICE_ENCRYPTION + * @hide + */ + public Builder setCriticalToDeviceEncryption(boolean critical) { + mCriticalToDeviceEncryption = critical; + return this; + } + + /** * Builds an instance of {@code KeyGenParameterSpec}. */ @NonNull @@ -1370,7 +1399,8 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mInvalidatedByBiometricEnrollment, mIsStrongBoxBacked, mUserConfirmationRequired, - mUnlockedDeviceRequired); + mUnlockedDeviceRequired, + mCriticalToDeviceEncryption); } } } diff --git a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java index d8030fb8ab79..98e458930a7f 100644 --- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java @@ -16,8 +16,8 @@ package android.security.keystore; -import android.os.Parcelable; import android.os.Parcel; +import android.os.Parcelable; import java.math.BigInteger; import java.security.spec.AlgorithmParameterSpec; @@ -105,6 +105,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable { out.writeBoolean(mSpec.isStrongBoxBacked()); out.writeBoolean(mSpec.isUserConfirmationRequired()); out.writeBoolean(mSpec.isUnlockedDeviceRequired()); + out.writeBoolean(mSpec.isCriticalToDeviceEncryption()); } private static Date readDateOrNull(Parcel in) { @@ -160,6 +161,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable { final boolean isStrongBoxBacked = in.readBoolean(); final boolean userConfirmationRequired = in.readBoolean(); final boolean unlockedDeviceRequired = in.readBoolean(); + final boolean criticalToDeviceEncryption = in.readBoolean(); // The KeyGenParameterSpec is intentionally not constructed using a Builder here: // The intention is for this class to break if new parameters are added to the // KeyGenParameterSpec constructor (whereas using a builder would silently drop them). @@ -190,7 +192,8 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable { invalidatedByBiometricEnrollment, isStrongBoxBacked, userConfirmationRequired, - unlockedDeviceRequired); + unlockedDeviceRequired, + criticalToDeviceEncryption); } public static final @android.annotation.NonNull Creator<ParcelableKeyGenParameterSpec> CREATOR = new Creator<ParcelableKeyGenParameterSpec>() { diff --git a/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java b/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java index fca2775a34bb..b7d72fce6eba 100644 --- a/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java +++ b/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java @@ -84,6 +84,7 @@ public final class ParcelableKeyGenParameterSpecTest { .setIsStrongBoxBacked(true) .setUserConfirmationRequired(true) .setUnlockedDeviceRequired(true) + .setCriticalToDeviceEncryption(true) .build(); } @@ -115,6 +116,7 @@ public final class ParcelableKeyGenParameterSpecTest { assertThat(spec.isStrongBoxBacked(), is(true)); assertThat(spec.isUserConfirmationRequired(), is(true)); assertThat(spec.isUnlockedDeviceRequired(), is(true)); + assertThat(spec.isCriticalToDeviceEncryption(), is(true)); } private Parcel parcelForReading(ParcelableKeyGenParameterSpec spec) { diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index a7f8cc4688ef..51270f5bcebd 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -25,11 +25,6 @@ cc_defaults { // GCC false-positives on this warning, and since we -Werror that's // a problem "-Wno-free-nonheap-object", - - // Clang is producing non-determistic binary when the new pass manager is - // enabled. Disable the new PM as a temporary workaround. - // b/142372146 - "-fno-experimental-new-pass-manager", ], include_dirs: [ diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index 12681ae221d5..5790150a3425 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -217,13 +217,16 @@ public: canvas->setMatrix(mMatrix); switch (mType) { case Type::Rect: - canvas->clipRect(mRRect.rect(), mOp); + // Don't anti-alias rectangular clips + canvas->clipRect(mRRect.rect(), mOp, false); break; case Type::RRect: - canvas->clipRRect(mRRect, mOp); + // Ensure rounded rectangular clips are anti-aliased + canvas->clipRRect(mRRect, mOp, true); break; case Type::Path: - canvas->clipPath(mPath.value(), mOp); + // Ensure path clips are anti-aliased + canvas->clipPath(mPath.value(), mOp, true); break; } } @@ -392,7 +395,7 @@ bool SkiaCanvas::clipRect(float left, float top, float right, float bottom, SkCl bool SkiaCanvas::clipPath(const SkPath* path, SkClipOp op) { this->recordClip(*path, op); - mCanvas->clipPath(*path, op); + mCanvas->clipPath(*path, op, true); return !mCanvas->isClipEmpty(); } diff --git a/libs/hwui/tests/common/TestContext.cpp b/libs/hwui/tests/common/TestContext.cpp index 0a54aca4970d..e075d806126b 100644 --- a/libs/hwui/tests/common/TestContext.cpp +++ b/libs/hwui/tests/common/TestContext.cpp @@ -25,16 +25,16 @@ namespace test { static const int IDENT_DISPLAYEVENT = 1; static android::DisplayInfo DUMMY_DISPLAY{ - 1080, // w - 1920, // h - 320.0, // xdpi - 320.0, // ydpi - 60.0, // fps - 2.0, // density - 0, // orientation - false, // secure? - 0, // appVsyncOffset - 0, // presentationDeadline + 1080, // w + 1920, // h + 320.0, // xdpi + 320.0, // ydpi + 60.0, // fps + 2.0, // density + ui::ROTATION_0, // orientation + false, // secure? + 0, // appVsyncOffset + 0, // presentationDeadline }; DisplayInfo getInternalDisplay() { diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp index b93759f87da2..c445885c5c63 100644 --- a/libs/hwui/utils/Color.cpp +++ b/libs/hwui/utils/Color.cpp @@ -108,10 +108,26 @@ SkColorType PixelFormatToColorType(android::PixelFormat format) { } } +// FIXME: Share with the version in android_bitmap.cpp? +// Skia's SkNamedGamut::kDCIP3 is based on a white point of D65. This gamut +// matches the white point used by ColorSpace.Named.DCIP3. +static constexpr skcms_Matrix3x3 kDCIP3 = {{ + {0.486143, 0.323835, 0.154234}, + {0.226676, 0.710327, 0.0629966}, + {0.000800549, 0.0432385, 0.78275}, +}}; + sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace) { if (dataspace == HAL_DATASPACE_UNKNOWN) { return SkColorSpace::MakeSRGB(); } + if (dataspace == HAL_DATASPACE_DCI_P3) { + // This cannot be handled by the switch statements below because it + // needs to use the locally-defined kDCIP3 gamut, rather than the one in + // Skia (SkNamedGamut), which is used for other data spaces with + // HAL_DATASPACE_STANDARD_DCI_P3 (e.g. HAL_DATASPACE_DISPLAY_P3). + return SkColorSpace::MakeRGB({2.6f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, kDCIP3); + } skcms_Matrix3x3 gamut; switch (dataspace & HAL_DATASPACE_STANDARD_MASK) { @@ -152,10 +168,12 @@ sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace) { return SkColorSpace::MakeRGB({2.6f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, gamut); case HAL_DATASPACE_TRANSFER_GAMMA2_8: return SkColorSpace::MakeRGB({2.8f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, gamut); + case HAL_DATASPACE_TRANSFER_ST2084: + return SkColorSpace::MakeRGB(SkNamedTransferFn::kPQ, gamut); + case HAL_DATASPACE_TRANSFER_SMPTE_170M: + return SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, gamut); case HAL_DATASPACE_TRANSFER_UNSPECIFIED: return nullptr; - case HAL_DATASPACE_TRANSFER_SMPTE_170M: - case HAL_DATASPACE_TRANSFER_ST2084: case HAL_DATASPACE_TRANSFER_HLG: default: ALOGV("Unsupported Gamma: %d", dataspace); diff --git a/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java b/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java index 751bb6a70880..127d00c0afe2 100644 --- a/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java +++ b/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java @@ -110,7 +110,6 @@ public class GnssMetrics { * Logs the status of a location report received from the HAL */ public void logReceivedLocationStatus(boolean isSuccessful) { - StatsLog.write(StatsLog.GPS_LOCATION_STATUS_REPORTED, isSuccessful); if (!isSuccessful) { mLocationFailureStatistics.addItem(1.0); return; @@ -127,7 +126,6 @@ public class GnssMetrics { DEFAULT_TIME_BETWEEN_FIXES_MILLISECS, desiredTimeBetweenFixesMilliSeconds)) - 1; if (numReportMissed > 0) { for (int i = 0; i < numReportMissed; i++) { - StatsLog.write(StatsLog.GPS_LOCATION_STATUS_REPORTED, false); mLocationFailureStatistics.addItem(1.0); } } @@ -138,7 +136,6 @@ public class GnssMetrics { */ public void logTimeToFirstFixMilliSecs(int timeToFirstFixMilliSeconds) { mTimeToFirstFixSecStatistics.addItem((double) (timeToFirstFixMilliSeconds / 1000)); - StatsLog.write(StatsLog.GPS_TIME_TO_FIRST_FIX_REPORTED, timeToFirstFixMilliSeconds); } /** diff --git a/media/OWNERS b/media/OWNERS index 8bd037a14150..be605831a24b 100644 --- a/media/OWNERS +++ b/media/OWNERS @@ -5,6 +5,7 @@ elaurent@google.com etalvala@google.com gkasten@google.com hdmoon@google.com +hkuang@google.com hunga@google.com insun@google.com jaewan@google.com diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java index ece53353c871..8cd3c6e64b78 100644 --- a/media/java/android/media/AudioAttributes.java +++ b/media/java/android/media/AudioAttributes.java @@ -598,6 +598,11 @@ public final class AudioAttributes implements Parcelable { private boolean mMuteHapticChannels = true; private HashSet<String> mTags = new HashSet<String>(); private Bundle mBundle; + private int mPrivacySensitive = PRIVACY_SENSITIVE_DEFAULT; + + private static final int PRIVACY_SENSITIVE_DEFAULT = -1; + private static final int PRIVACY_SENSITIVE_DISABLED = 0; + private static final int PRIVACY_SENSITIVE_ENABLED = 1; /** * Constructs a new Builder with the defaults. @@ -638,11 +643,20 @@ public final class AudioAttributes implements Parcelable { if (mMuteHapticChannels) { aa.mFlags |= FLAG_MUTE_HAPTIC; } - // capturing for camcorder of communication is private by default to - // reflect legacy behavior - if (aa.mSource == MediaRecorder.AudioSource.VOICE_COMMUNICATION - || aa.mSource == MediaRecorder.AudioSource.CAMCORDER) { + + if (mPrivacySensitive == PRIVACY_SENSITIVE_DEFAULT) { + // capturing for camcorder or communication is private by default to + // reflect legacy behavior + if (mSource == MediaRecorder.AudioSource.VOICE_COMMUNICATION + || mSource == MediaRecorder.AudioSource.CAMCORDER) { + aa.mFlags |= FLAG_CAPTURE_PRIVATE; + } else { + aa.mFlags &= ~FLAG_CAPTURE_PRIVATE; + } + } else if (mPrivacySensitive == PRIVACY_SENSITIVE_ENABLED) { aa.mFlags |= FLAG_CAPTURE_PRIVATE; + } else { + aa.mFlags &= ~FLAG_CAPTURE_PRIVATE; } aa.mTags = (HashSet<String>) mTags.clone(); aa.mFormattedTags = TextUtils.join(";", mTags); @@ -967,6 +981,20 @@ public final class AudioAttributes implements Parcelable { mMuteHapticChannels = muted; return this; } + + /** + * @hide + * Indicates if an AudioRecord build with this AudioAttributes is privacy sensitive or not. + * See {@link AudioRecord.Builder#setPrivacySensitive(boolean)}. + * @param privacySensitive True if capture must be marked as privacy sensitive, + * false otherwise. + * @return the same Builder instance. + */ + public @NonNull Builder setPrivacySensitive(boolean privacySensitive) { + mPrivacySensitive = + privacySensitive ? PRIVACY_SENSITIVE_ENABLED : PRIVACY_SENSITIVE_DISABLED; + return this; + } }; @Override diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java index fd3523de4d0f..4d26b8d3f7af 100644 --- a/media/java/android/media/AudioRecord.java +++ b/media/java/android/media/AudioRecord.java @@ -315,7 +315,7 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, * @hide * Class constructor with {@link AudioAttributes} and {@link AudioFormat}. * @param attributes a non-null {@link AudioAttributes} instance. Use - * {@link AudioAttributes.Builder#setAudioSource(int)} for configuring the audio + * {@link AudioAttributes.Builder#setCapturePreset(int)} for configuring the audio * source for this instance. * @param format a non-null {@link AudioFormat} instance describing the format of the data * that will be recorded through this AudioRecord. See {@link AudioFormat.Builder} for @@ -754,17 +754,10 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, "Cannot request private capture with source: " + source); } - int flags = mAttributes.getAllFlags(); - if (mPrivacySensitive == PRIVACY_SENSITIVE_DISABLED) { - flags &= ~AudioAttributes.FLAG_CAPTURE_PRIVATE; - } else if (mPrivacySensitive == PRIVACY_SENSITIVE_ENABLED) { - flags |= AudioAttributes.FLAG_CAPTURE_PRIVATE; - } - if (flags != mAttributes.getAllFlags()) { - mAttributes = new AudioAttributes.Builder(mAttributes) - .replaceFlags(flags) - .build(); - } + mAttributes = new AudioAttributes.Builder(mAttributes) + .setInternalCapturePreset(source) + .setPrivacySensitive(mPrivacySensitive == PRIVACY_SENSITIVE_ENABLED) + .build(); } try { diff --git a/media/java/android/media/IMediaRoute2Provider.aidl b/media/java/android/media/IMediaRoute2Provider.aidl index 5dd0b1c915bd..aa38e5148069 100644 --- a/media/java/android/media/IMediaRoute2Provider.aidl +++ b/media/java/android/media/IMediaRoute2Provider.aidl @@ -24,8 +24,7 @@ import android.media.IMediaRoute2ProviderClient; */ oneway interface IMediaRoute2Provider { void setClient(IMediaRoute2ProviderClient client); - void requestCreateSession(String packageName, String routeId, - String routeFeature, long requestId); + void requestCreateSession(String packageName, String routeId, long requestId); void releaseSession(String sessionId); void selectRoute(String sessionId, String routeId); diff --git a/media/java/android/media/IMediaRoute2ProviderClient.aidl b/media/java/android/media/IMediaRoute2ProviderClient.aidl index e8abfdca89b7..0fccb3a9aeac 100644 --- a/media/java/android/media/IMediaRoute2ProviderClient.aidl +++ b/media/java/android/media/IMediaRoute2ProviderClient.aidl @@ -25,8 +25,10 @@ import android.os.Bundle; * @hide */ oneway interface IMediaRoute2ProviderClient { - void updateState(in MediaRoute2ProviderInfo providerInfo, - in List<RoutingSessionInfo> sessionInfos); - void notifySessionCreated(in @nullable RoutingSessionInfo sessionInfo, long requestId); - void notifySessionInfoChanged(in RoutingSessionInfo sessionInfo); + // TODO: Change it to updateRoutes? + void updateState(in MediaRoute2ProviderInfo providerInfo); + void notifySessionCreated(in RoutingSessionInfo sessionInfo, long requestId); + void notifySessionCreationFailed(long requestId); + void notifySessionUpdated(in RoutingSessionInfo sessionInfo); + void notifySessionReleased(in RoutingSessionInfo sessionInfo); } diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl index 3cdaa0794b31..2d3e185e967c 100644 --- a/media/java/android/media/IMediaRouterService.aidl +++ b/media/java/android/media/IMediaRouterService.aidl @@ -47,12 +47,12 @@ interface IMediaRouterService { List<MediaRoute2Info> getSystemRoutes(); void registerClient2(IMediaRouter2Client client, String packageName); void unregisterClient2(IMediaRouter2Client client); - void sendControlRequest(IMediaRouter2Client client, in MediaRoute2Info route, in Intent request); + void sendControlRequest(IMediaRouter2Client client, in MediaRoute2Info route, + in Intent request); void requestSetVolume2(IMediaRouter2Client client, in MediaRoute2Info route, int volume); void requestUpdateVolume2(IMediaRouter2Client client, in MediaRoute2Info route, int direction); - void requestCreateSession(IMediaRouter2Client client, in MediaRoute2Info route, - String routeFeature, int requestId); + void requestCreateSession(IMediaRouter2Client client, in MediaRoute2Info route, int requestId); void setDiscoveryRequest2(IMediaRouter2Client client, in RouteDiscoveryPreference preference); void selectRoute(IMediaRouter2Client client, String sessionId, in MediaRoute2Info route); void deselectRoute(IMediaRouter2Client client, String sessionId, in MediaRoute2Info route); diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java index 021e23e190b3..239dfed051ee 100644 --- a/media/java/android/media/MediaRoute2Info.java +++ b/media/java/android/media/MediaRoute2Info.java @@ -134,105 +134,52 @@ public final class MediaRoute2Info implements Parcelable { */ public static final int DEVICE_TYPE_BLUETOOTH = 3; - @NonNull final String mId; - @Nullable - final String mProviderId; - @NonNull final CharSequence mName; - @Nullable - final CharSequence mDescription; - @Nullable - final @ConnectionState int mConnectionState; - @Nullable + final List<String> mFeatures; + @DeviceType + final int mDeviceType; final Uri mIconUri; - @Nullable + final CharSequence mDescription; + @ConnectionState + final int mConnectionState; final String mClientPackageName; - @NonNull - final List<String> mFeatures; final int mVolume; final int mVolumeMax; final int mVolumeHandling; - final @DeviceType int mDeviceType; - @Nullable final Bundle mExtras; + final String mProviderId; MediaRoute2Info(@NonNull Builder builder) { mId = builder.mId; - mProviderId = builder.mProviderId; mName = builder.mName; + mFeatures = builder.mFeatures; + mDeviceType = builder.mDeviceType; + mIconUri = builder.mIconUri; mDescription = builder.mDescription; mConnectionState = builder.mConnectionState; - mIconUri = builder.mIconUri; mClientPackageName = builder.mClientPackageName; - mFeatures = builder.mFeatures; - mVolume = builder.mVolume; - mVolumeMax = builder.mVolumeMax; mVolumeHandling = builder.mVolumeHandling; - mDeviceType = builder.mDeviceType; + mVolumeMax = builder.mVolumeMax; + mVolume = builder.mVolume; mExtras = builder.mExtras; + mProviderId = builder.mProviderId; } MediaRoute2Info(@NonNull Parcel in) { mId = in.readString(); - mProviderId = in.readString(); mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + mFeatures = in.createStringArrayList(); + mDeviceType = in.readInt(); + mIconUri = in.readParcelable(null); mDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); mConnectionState = in.readInt(); - mIconUri = in.readParcelable(null); mClientPackageName = in.readString(); - mFeatures = in.createStringArrayList(); - mVolume = in.readInt(); - mVolumeMax = in.readInt(); mVolumeHandling = in.readInt(); - mDeviceType = in.readInt(); + mVolumeMax = in.readInt(); + mVolume = in.readInt(); mExtras = in.readBundle(); - } - - /** - * Returns true if the route info has all of the required field. - * A route info only obtained from {@link com.android.server.media.MediaRouterService} - * is valid. - * @hide - */ - //TODO: Reconsider the validity of a route info when fields are added. - public boolean isValid() { - if (TextUtils.isEmpty(getId()) || TextUtils.isEmpty(getName()) - || TextUtils.isEmpty(getProviderId())) { - return false; - } - return true; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof MediaRoute2Info)) { - return false; - } - MediaRoute2Info other = (MediaRoute2Info) obj; - return Objects.equals(mId, other.mId) - && Objects.equals(mProviderId, other.mProviderId) - && Objects.equals(mName, other.mName) - && Objects.equals(mDescription, other.mDescription) - && (mConnectionState == other.mConnectionState) - && Objects.equals(mIconUri, other.mIconUri) - && Objects.equals(mClientPackageName, other.mClientPackageName) - && Objects.equals(mFeatures, other.mFeatures) - && (mVolume == other.mVolume) - && (mVolumeMax == other.mVolumeMax) - && (mVolumeHandling == other.mVolumeHandling) - && (mDeviceType == other.mDeviceType) - //TODO: This will be evaluated as false in most cases. Try not to. - && Objects.equals(mExtras, other.mExtras); - } - - @Override - public int hashCode() { - return Objects.hash(mId, mName, mDescription, mConnectionState, mIconUri, - mFeatures, mVolume, mVolumeMax, mVolumeHandling, mDeviceType); + mProviderId = in.readString(); } /** @@ -254,31 +201,48 @@ public final class MediaRoute2Info implements Parcelable { } /** - * Gets the original id set by {@link Builder#Builder(String, CharSequence)}. - * @hide + * Gets the user-visible name of the route. */ @NonNull - public String getOriginalId() { - return mId; + public CharSequence getName() { + return mName; } /** - * Gets the provider id of the route. It is assigned automatically by - * {@link com.android.server.media.MediaRouterService}. + * Gets the supported features of the route. + */ + @NonNull + public List<String> getFeatures() { + return mFeatures; + } + + /** + * Gets the type of the receiver device associated with this route. * - * @return provider id of the route or null if it's not set. - * @hide + * @return The type of the receiver device associated with this route: + * {@link #DEVICE_TYPE_REMOTE_TV}, {@link #DEVICE_TYPE_REMOTE_SPEAKER}, + * {@link #DEVICE_TYPE_BLUETOOTH}. */ - @Nullable - public String getProviderId() { - return mProviderId; + @DeviceType + public int getDeviceType() { + return mDeviceType; } - @NonNull - public CharSequence getName() { - return mName; + /** + * Gets the URI of the icon representing this route. + * <p> + * This icon will be used in picker UIs if available. + * + * @return The URI of the icon representing this route, or null if none. + */ + @Nullable + public Uri getIconUri() { + return mIconUri; } + /** + * Gets the user-visible description of the route. + */ @Nullable public CharSequence getDescription() { return mDescription; @@ -296,20 +260,8 @@ public final class MediaRoute2Info implements Parcelable { } /** - * Gets the URI of the icon representing this route. - * <p> - * This icon will be used in picker UIs if available. - * - * @return The URI of the icon representing this route, or null if none. - */ - @Nullable - public Uri getIconUri() { - return mIconUri; - } - - /** * Gets the package name of the client that uses the route. - * Returns null if no clients use this. + * Returns null if no clients use this route. * @hide */ @Nullable @@ -318,23 +270,19 @@ public final class MediaRoute2Info implements Parcelable { } /** - * Gets the supported categories of the route. + * Gets information about how volume is handled on the route. + * + * @return {@link #PLAYBACK_VOLUME_FIXED} or {@link #PLAYBACK_VOLUME_VARIABLE} */ - @NonNull - public List<String> getFeatures() { - return mFeatures; + public int getVolumeHandling() { + return mVolumeHandling; } /** - * Gets the type of the receiver device associated with this route. - * - * @return The type of the receiver device associated with this route: - * {@link #DEVICE_TYPE_REMOTE_TV}, {@link #DEVICE_TYPE_REMOTE_SPEAKER}, - * {@link #DEVICE_TYPE_BLUETOOTH}. + * Gets the maximum volume of the route. */ - @DeviceType - public int getDeviceType() { - return mDeviceType; + public int getVolumeMax() { + return mVolumeMax; } /** @@ -344,25 +292,30 @@ public final class MediaRoute2Info implements Parcelable { return mVolume; } + @Nullable + public Bundle getExtras() { + return mExtras == null ? null : new Bundle(mExtras); + } + /** - * Gets the maximum volume of the route. + * Gets the original id set by {@link Builder#Builder(String, CharSequence)}. + * @hide */ - public int getVolumeMax() { - return mVolumeMax; + @NonNull + public String getOriginalId() { + return mId; } /** - * Gets information about how volume is handled on the route. + * Gets the provider id of the route. It is assigned automatically by + * {@link com.android.server.media.MediaRouterService}. * - * @return {@link #PLAYBACK_VOLUME_FIXED} or {@link #PLAYBACK_VOLUME_VARIABLE} + * @return provider id of the route or null if it's not set. + * @hide */ - public int getVolumeHandling() { - return mVolumeHandling; - } - @Nullable - public Bundle getExtras() { - return mExtras; + public String getProviderId() { + return mProviderId; } /** @@ -381,65 +334,117 @@ public final class MediaRoute2Info implements Parcelable { return false; } + /** + * Returns true if the route info has all of the required field. + * A route info only obtained from {@link com.android.server.media.MediaRouterService} + * is valid. + * @hide + */ + //TODO: Reconsider the validity of a route info when fields are added. + public boolean isValid() { + if (TextUtils.isEmpty(getId()) || TextUtils.isEmpty(getName()) + || TextUtils.isEmpty(getProviderId())) { + return false; + } + return true; + } + @Override - public int describeContents() { - return 0; + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof MediaRoute2Info)) { + return false; + } + MediaRoute2Info other = (MediaRoute2Info) obj; + + // Note: mExtras is not included. + return Objects.equals(mId, other.mId) + && Objects.equals(mName, other.mName) + && Objects.equals(mFeatures, other.mFeatures) + && (mDeviceType == other.mDeviceType) + && Objects.equals(mIconUri, other.mIconUri) + && Objects.equals(mDescription, other.mDescription) + && (mConnectionState == other.mConnectionState) + && Objects.equals(mClientPackageName, other.mClientPackageName) + && (mVolumeHandling == other.mVolumeHandling) + && (mVolumeMax == other.mVolumeMax) + && (mVolume == other.mVolume) + && Objects.equals(mProviderId, other.mProviderId); } @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeString(mId); - dest.writeString(mProviderId); - TextUtils.writeToParcel(mName, dest, flags); - TextUtils.writeToParcel(mDescription, dest, flags); - dest.writeInt(mConnectionState); - dest.writeParcelable(mIconUri, flags); - dest.writeString(mClientPackageName); - dest.writeStringList(mFeatures); - dest.writeInt(mVolume); - dest.writeInt(mVolumeMax); - dest.writeInt(mVolumeHandling); - dest.writeInt(mDeviceType); - dest.writeBundle(mExtras); + public int hashCode() { + // Note: mExtras is not included. + return Objects.hash(mId, mName, mFeatures, mDeviceType, mIconUri, mDescription, + mConnectionState, mClientPackageName, mVolumeHandling, mVolumeMax, mVolume, + mProviderId); } @Override public String toString() { + // Note: mExtras is not printed here. StringBuilder result = new StringBuilder() - .append("MediaRouteInfo{ ") + .append("MediaRoute2Info{ ") .append("id=").append(getId()) .append(", name=").append(getName()) + .append(", features=").append(getFeatures()) + .append(", deviceType=").append(getDeviceType()) + .append(", iconUri=").append(getIconUri()) .append(", description=").append(getDescription()) .append(", connectionState=").append(getConnectionState()) - .append(", iconUri=").append(getIconUri()) - .append(", volume=").append(getVolume()) - .append(", volumeMax=").append(getVolumeMax()) + .append(", clientPackageName=").append(getClientPackageName()) .append(", volumeHandling=").append(getVolumeHandling()) - .append(", deviceType=").append(getDeviceType()) + .append(", volumeMax=").append(getVolumeMax()) + .append(", volume=").append(getVolume()) .append(", providerId=").append(getProviderId()) .append(" }"); return result.toString(); } + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(mId); + TextUtils.writeToParcel(mName, dest, flags); + dest.writeStringList(mFeatures); + dest.writeInt(mDeviceType); + dest.writeParcelable(mIconUri, flags); + TextUtils.writeToParcel(mDescription, dest, flags); + dest.writeInt(mConnectionState); + dest.writeString(mClientPackageName); + dest.writeInt(mVolumeHandling); + dest.writeInt(mVolumeMax); + dest.writeInt(mVolume); + dest.writeBundle(mExtras); + dest.writeString(mProviderId); + } + /** * Builder for {@link MediaRoute2Info media route info}. */ public static final class Builder { final String mId; - String mProviderId; final CharSequence mName; + final List<String> mFeatures; + + @DeviceType + int mDeviceType = DEVICE_TYPE_UNKNOWN; + Uri mIconUri; CharSequence mDescription; @ConnectionState int mConnectionState; - Uri mIconUri; String mClientPackageName; - List<String> mFeatures; - int mVolume; - int mVolumeMax; int mVolumeHandling = PLAYBACK_VOLUME_FIXED; - @DeviceType - int mDeviceType = DEVICE_TYPE_UNKNOWN; + int mVolumeMax; + int mVolume; Bundle mExtras; + String mProviderId; /** * Constructor for builder to create {@link MediaRoute2Info}. @@ -448,8 +453,8 @@ public final class MediaRoute2Info implements Parcelable { * obtained from {@link MediaRouter2} can be different from what was set in * {@link MediaRoute2ProviderService}. * </p> - * @param id - * @param name + * @param id The ID of the route. Must not be empty. + * @param name The user-visible name of the route. */ public Builder(@NonNull String id, @NonNull CharSequence name) { if (TextUtils.isEmpty(id)) { @@ -463,62 +468,71 @@ public final class MediaRoute2Info implements Parcelable { mFeatures = new ArrayList<>(); } + /** + * Constructor for builder to create {@link MediaRoute2Info} with + * existing {@link MediaRoute2Info} instance. + * + * @param routeInfo the existing instance to copy data from. + */ public Builder(@NonNull MediaRoute2Info routeInfo) { - if (routeInfo == null) { - throw new IllegalArgumentException("route info must not be null"); - } + Objects.requireNonNull(routeInfo, "routeInfo must not be null"); + mId = routeInfo.mId; mName = routeInfo.mName; - - if (!TextUtils.isEmpty(routeInfo.mProviderId)) { - setProviderId(routeInfo.mProviderId); - } + mFeatures = new ArrayList<>(routeInfo.mFeatures); + mDeviceType = routeInfo.mDeviceType; + mIconUri = routeInfo.mIconUri; mDescription = routeInfo.mDescription; mConnectionState = routeInfo.mConnectionState; - mIconUri = routeInfo.mIconUri; - setClientPackageName(routeInfo.mClientPackageName); - mFeatures = new ArrayList<>(routeInfo.mFeatures); - setVolume(routeInfo.mVolume); - setVolumeMax(routeInfo.mVolumeMax); - setVolumeHandling(routeInfo.mVolumeHandling); - setDeviceType(routeInfo.mDeviceType); + mClientPackageName = routeInfo.mClientPackageName; + mVolumeHandling = routeInfo.mVolumeHandling; + mVolumeMax = routeInfo.mVolumeMax; + mVolume = routeInfo.mVolume; if (routeInfo.mExtras != null) { mExtras = new Bundle(routeInfo.mExtras); } + mProviderId = routeInfo.mProviderId; } /** - * Sets the provider id of the route. - * @hide + * Adds a feature for the route. */ @NonNull - public Builder setProviderId(@NonNull String providerId) { - if (TextUtils.isEmpty(providerId)) { - throw new IllegalArgumentException("providerId must not be null or empty"); + public Builder addFeature(@NonNull String feature) { + if (TextUtils.isEmpty(feature)) { + throw new IllegalArgumentException("feature must not be null or empty"); } - mProviderId = providerId; + mFeatures.add(feature); return this; } /** - * Sets the user-visible description of the route. + * Adds features for the route. A route must support at least one route type. */ @NonNull - public Builder setDescription(@Nullable CharSequence description) { - mDescription = description; + public Builder addFeatures(@NonNull Collection<String> features) { + Objects.requireNonNull(features, "features must not be null"); + for (String feature : features) { + addFeature(feature); + } return this; } /** - * Sets the route's connection state. - * - * {@link #CONNECTION_STATE_DISCONNECTED}, - * {@link #CONNECTION_STATE_CONNECTING}, or - * {@link #CONNECTION_STATE_CONNECTED}. - */ + * Clears the features of the route. A route must support at least one route type. + */ @NonNull - public Builder setConnectionState(@ConnectionState int connectionState) { - mConnectionState = connectionState; + public Builder clearFeatures() { + mFeatures.clear(); + return this; + } + + /** + * Sets the route's device type. + */ + @NonNull + public Builder setDeviceType(@DeviceType int deviceType) { + mDeviceType = deviceType; return this; } @@ -543,53 +557,42 @@ public final class MediaRoute2Info implements Parcelable { } /** - * Sets the package name of the app using the route. - */ - @NonNull - public Builder setClientPackageName(@Nullable String packageName) { - mClientPackageName = packageName; - return this; - } - - /** - * Clears the features of the route. + * Sets the user-visible description of the route. */ @NonNull - public Builder clearFeatures() { - mFeatures = new ArrayList<>(); + public Builder setDescription(@Nullable CharSequence description) { + mDescription = description; return this; } /** - * Adds features for the route. - */ + * Sets the route's connection state. + * + * {@link #CONNECTION_STATE_DISCONNECTED}, + * {@link #CONNECTION_STATE_CONNECTING}, or + * {@link #CONNECTION_STATE_CONNECTED}. + */ @NonNull - public Builder addFeatures(@NonNull Collection<String> features) { - Objects.requireNonNull(features, "features must not be null"); - for (String feature : features) { - addFeature(feature); - } + public Builder setConnectionState(@ConnectionState int connectionState) { + mConnectionState = connectionState; return this; } /** - * Adds a feature for the route. + * Sets the package name of the app using the route. */ @NonNull - public Builder addFeature(@NonNull String feature) { - if (TextUtils.isEmpty(feature)) { - throw new IllegalArgumentException("feature must not be null or empty"); - } - mFeatures.add(feature); + public Builder setClientPackageName(@Nullable String packageName) { + mClientPackageName = packageName; return this; } /** - * Sets the route's current volume, or 0 if unknown. + * Sets the route's volume handling. */ @NonNull - public Builder setVolume(int volume) { - mVolume = volume; + public Builder setVolumeHandling(int volumeHandling) { + mVolumeHandling = volumeHandling; return this; } @@ -603,37 +606,52 @@ public final class MediaRoute2Info implements Parcelable { } /** - * Sets the route's volume handling. + * Sets the route's current volume, or 0 if unknown. */ @NonNull - public Builder setVolumeHandling(int volumeHandling) { - mVolumeHandling = volumeHandling; + public Builder setVolume(int volume) { + mVolume = volume; return this; } /** - * Sets the route's device type. + * Sets a bundle of extras for the route. + * <p> + * Note: The extras will not affect the result of {@link MediaRoute2Info#equals(Object)}. */ @NonNull - public Builder setDeviceType(@DeviceType int deviceType) { - mDeviceType = deviceType; + public Builder setExtras(@Nullable Bundle extras) { + if (extras == null) { + mExtras = null; + return this; + } + mExtras = new Bundle(extras); return this; } /** - * Sets a bundle of extras for the route. + * Sets the provider id of the route. + * @hide */ @NonNull - public Builder setExtras(@Nullable Bundle extras) { - mExtras = new Bundle(extras); + public Builder setProviderId(@NonNull String providerId) { + if (TextUtils.isEmpty(providerId)) { + throw new IllegalArgumentException("providerId must not be null or empty"); + } + mProviderId = providerId; return this; } /** * Builds the {@link MediaRoute2Info media route info}. + * + * @throws IllegalArgumentException if no features are added. */ @NonNull public MediaRoute2Info build() { + if (mFeatures.isEmpty()) { + throw new IllegalArgumentException("features must not be empty!"); + } return new MediaRoute2Info(this); } } diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java index 1cd5dfa087fa..5a3de6d60277 100644 --- a/media/java/android/media/MediaRoute2ProviderService.java +++ b/media/java/android/media/MediaRoute2ProviderService.java @@ -58,6 +58,16 @@ public abstract class MediaRoute2ProviderService extends Service { public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService"; + /** + * The request ID to pass {@link #notifySessionCreated(RoutingSessionInfo, long)} + * when {@link MediaRoute2ProviderService} created a session although there was no creation + * request. + * + * @see #notifySessionCreated(RoutingSessionInfo, long) + * @hide + */ + public static final long REQUEST_ID_UNKNOWN = 0; + private final Handler mHandler; private final Object mSessionLock = new Object(); private final AtomicBoolean mStatePublishScheduled = new AtomicBoolean(false); @@ -118,7 +128,7 @@ public abstract class MediaRoute2ProviderService extends Service { * * @param sessionId id of the session * @return information of the session with the given id. - * null if the session is destroyed or id is not valid. + * null if the session is released or ID is not valid. * @hide */ @Nullable @@ -143,161 +153,177 @@ public abstract class MediaRoute2ProviderService extends Service { } /** - * Updates the information of a session. - * If the session is destroyed or not created before, it will be ignored. - * Call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify clients of - * session info changes. + * Notifies clients of that the session is created and ready for use. + * <p> + * If this session is created without any creation request, use {@link #REQUEST_ID_UNKNOWN} + * as the request ID. * - * @param sessionInfo new session information - * @see #notifySessionCreated(RoutingSessionInfo, long) + * @param sessionInfo information of the new session. + * The {@link RoutingSessionInfo#getId() id} of the session must be unique. + * @param requestId id of the previous request to create this session provided in + * {@link #onCreateSession(String, String, long)} + * @see #onCreateSession(String, String, long) * @hide */ - public final void updateSessionInfo(@NonNull RoutingSessionInfo sessionInfo) { + public final void notifySessionCreated(@NonNull RoutingSessionInfo sessionInfo, + long requestId) { Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); - String sessionId = sessionInfo.getId(); + String sessionId = sessionInfo.getId(); synchronized (mSessionLock) { if (mSessionInfo.containsKey(sessionId)) { - mSessionInfo.put(sessionId, sessionInfo); - schedulePublishState(); - } else { - Log.w(TAG, "Ignoring unknown session info."); + Log.w(TAG, "Ignoring duplicate session id."); return; } + mSessionInfo.put(sessionInfo.getId(), sessionInfo); + } + schedulePublishState(); + + if (mClient == null) { + return; + } + try { + // TODO: Calling binder calls in multiple thread may cause timing issue. + // Consider to change implementations to avoid the problems. + // For example, post binder calls, always send all sessions at once, etc. + mClient.notifySessionCreated(sessionInfo, requestId); + } catch (RemoteException ex) { + Log.w(TAG, "Failed to notify session created."); } } /** - * Notifies the session is changed. + * Notifies clients of that the session could not be created. * - * TODO: This method is temporary, only created for tests. Remove when the alternative is ready. + * @param requestId id of the previous request to create the session provided in + * {@link #onCreateSession(String, String, long)}. + * @see #onCreateSession(String, String, long) * @hide */ - public final void notifySessionInfoChanged(@NonNull RoutingSessionInfo sessionInfo) { - Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); - - String sessionId = sessionInfo.getId(); - synchronized (mSessionLock) { - if (mSessionInfo.containsKey(sessionId)) { - mSessionInfo.put(sessionId, sessionInfo); - } else { - Log.w(TAG, "Ignoring unknown session info."); - return; - } - } - + public final void notifySessionCreationFailed(long requestId) { if (mClient == null) { return; } try { - mClient.notifySessionInfoChanged(sessionInfo); + mClient.notifySessionCreationFailed(requestId); } catch (RemoteException ex) { - Log.w(TAG, "Failed to notify session info changed."); + Log.w(TAG, "Failed to notify session creation failed."); } } /** - * Notifies clients of that the session is created and ready for use. If the session can be - * controlled, pass a {@link Bundle} that contains how to control it. + * Notifies the existing session is updated. For example, when + * {@link RoutingSessionInfo#getSelectedRoutes() selected routes} are changed. * - * @param sessionInfo information of the new session. - * The {@link RoutingSessionInfo#getId() id} of the session must be - * unique. Pass {@code null} to reject the request or inform clients that - * session creation is failed. - * @param requestId id of the previous request to create this session * @hide */ - // TODO: fail reason? - // TODO: Maybe better to create notifySessionCreationFailed? - public final void notifySessionCreated(@Nullable RoutingSessionInfo sessionInfo, - long requestId) { - if (sessionInfo != null) { - String sessionId = sessionInfo.getId(); - synchronized (mSessionLock) { - if (mSessionInfo.containsKey(sessionId)) { - Log.w(TAG, "Ignoring duplicate session id."); - return; - } - mSessionInfo.put(sessionInfo.getId(), sessionInfo); + public final void notifySessionUpdated(@NonNull RoutingSessionInfo sessionInfo) { + Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); + + String sessionId = sessionInfo.getId(); + synchronized (mSessionLock) { + if (mSessionInfo.containsKey(sessionId)) { + mSessionInfo.put(sessionId, sessionInfo); + } else { + Log.w(TAG, "Ignoring unknown session info."); + return; } - schedulePublishState(); } if (mClient == null) { return; } try { - mClient.notifySessionCreated(sessionInfo, requestId); + mClient.notifySessionUpdated(sessionInfo); } catch (RemoteException ex) { - Log.w(TAG, "Failed to notify session created."); + Log.w(TAG, "Failed to notify session info changed."); } } /** - * Releases a session with the given id. - * {@link #onDestroySession} is called if the session is released. + * Notifies that the session is released. * - * @param sessionId id of the session to be released - * @see #onDestroySession(String, RoutingSessionInfo) + * @param sessionId id of the released session. + * @see #onReleaseSession(String) * @hide */ - public final void releaseSession(@NonNull String sessionId) { + public final void notifySessionReleased(@NonNull String sessionId) { if (TextUtils.isEmpty(sessionId)) { throw new IllegalArgumentException("sessionId must not be empty"); } - //TODO: notify media router service of release. RoutingSessionInfo sessionInfo; synchronized (mSessionLock) { sessionInfo = mSessionInfo.remove(sessionId); } - if (sessionInfo != null) { - mHandler.sendMessage(obtainMessage( - MediaRoute2ProviderService::onDestroySession, this, sessionId, sessionInfo)); - schedulePublishState(); + + if (sessionInfo == null) { + Log.w(TAG, "Ignoring unknown session info."); + return; + } + + if (mClient == null) { + return; + } + try { + mClient.notifySessionReleased(sessionInfo); + } catch (RemoteException ex) { + Log.w(TAG, "Failed to notify session info changed."); } } /** - * Called when a session should be created. + * Called when the service receives a request to create a session. + * <p> * You should create and maintain your own session and notifies the client of * session info. Call {@link #notifySessionCreated(RoutingSessionInfo, long)} * with the given {@code requestId} to notify the information of a new session. - * If you can't create the session or want to reject the request, pass {@code null} - * as session info in {@link #notifySessionCreated(RoutingSessionInfo, long)} - * with the given {@code requestId}. + * The created session must have the same route feature and must include the given route + * specified by {@code routeId}. + * <p> + * If the session can be controlled, you can optionally pass the control hints to + * {@link RoutingSessionInfo.Builder#setControlHints(Bundle)}. Control hints is a + * {@link Bundle} which contains how to control the session. + * <p> + * If you can't create the session or want to reject the request, call + * {@link #notifySessionCreationFailed(long)} with the given {@code requestId}. * * @param packageName the package name of the application that selected the route * @param routeId the id of the route initially being connected - * @param routeFeature the route feature of the new session * @param requestId the id of this session creation request + * + * @see RoutingSessionInfo.Builder#Builder(String, String) + * @see RoutingSessionInfo.Builder#addSelectedRoute(String) + * @see RoutingSessionInfo.Builder#setControlHints(Bundle) * @hide */ public abstract void onCreateSession(@NonNull String packageName, @NonNull String routeId, - @NonNull String routeFeature, long requestId); + long requestId); /** - * Called when a session is about to be destroyed. - * You can clean up your session here. This can happen by the - * client or provider itself. + * Called when the session should be released. A client of the session or system can request + * a session to be released. + * <p> + * After releasing the session, call {@link #notifySessionReleased(String)} + * with the ID of the released session. + * + * Note: Calling {@link #notifySessionReleased(String)} will <em>NOT</em> trigger + * this method to be called. * - * @param sessionId id of the session being destroyed. - * @param lastSessionInfo information of the session being destroyed. - * @see #releaseSession(String) + * @param sessionId id of the session being released. + * @see #notifySessionReleased(String) + * @see #getSessionInfo(String) * @hide */ - public abstract void onDestroySession(@NonNull String sessionId, - @NonNull RoutingSessionInfo lastSessionInfo); + public abstract void onReleaseSession(@NonNull String sessionId); //TODO: make a way to reject the request /** * Called when a client requests selecting a route for the session. - * After the route is selected, call {@link #updateSessionInfo(RoutingSessionInfo)} to update - * session info and call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify - * clients of updated session info. + * After the route is selected, call {@link #notifySessionUpdated(RoutingSessionInfo)} + * to update session info. * * @param sessionId id of the session * @param routeId id of the route - * @see #updateSessionInfo(RoutingSessionInfo) * @hide */ public abstract void onSelectRoute(@NonNull String sessionId, @NonNull String routeId); @@ -305,9 +331,8 @@ public abstract class MediaRoute2ProviderService extends Service { //TODO: make a way to reject the request /** * Called when a client requests deselecting a route from the session. - * After the route is deselected, call {@link #updateSessionInfo(RoutingSessionInfo)} to update - * session info and call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify - * clients of updated session info. + * After the route is deselected, call {@link #notifySessionUpdated(RoutingSessionInfo)} + * to update session info. * * @param sessionId id of the session * @param routeId id of the route @@ -318,9 +343,8 @@ public abstract class MediaRoute2ProviderService extends Service { //TODO: make a way to reject the request /** * Called when a client requests transferring a session to a route. - * After the transfer is finished, call {@link #updateSessionInfo(RoutingSessionInfo)} to update - * session info and call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify - * clients of updated session info. + * After the transfer is finished, call {@link #notifySessionUpdated(RoutingSessionInfo)} + * to update session info. * * @param sessionId id of the session * @param routeId id of the route @@ -344,6 +368,8 @@ public abstract class MediaRoute2ProviderService extends Service { * </p> * * @param preference the new discovery preference + * + * TODO: This method needs tests. */ public void onDiscoveryPreferenceChanged(@NonNull RouteDiscoveryPreference preference) {} @@ -383,7 +409,7 @@ public abstract class MediaRoute2ProviderService extends Service { sessionInfos = new ArrayList<>(mSessionInfo.values()); } try { - mClient.updateState(mProviderInfo, sessionInfos); + mClient.updateState(mProviderInfo); } catch (RemoteException ex) { Log.w(TAG, "Failed to send onProviderInfoUpdated"); } @@ -406,15 +432,14 @@ public abstract class MediaRoute2ProviderService extends Service { } @Override - public void requestCreateSession(String packageName, String routeId, - String routeFeature, long requestId) { + public void requestCreateSession(String packageName, String routeId, long requestId) { if (!checkCallerisSystem()) { return; } mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onCreateSession, - MediaRoute2ProviderService.this, packageName, routeId, routeFeature, - requestId)); + MediaRoute2ProviderService.this, packageName, routeId, requestId)); } + @Override public void releaseSession(@NonNull String sessionId) { if (!checkCallerisSystem()) { @@ -424,7 +449,7 @@ public abstract class MediaRoute2ProviderService extends Service { Log.w(TAG, "releaseSession: Ignoring empty sessionId from system service."); return; } - mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::releaseSession, + mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onReleaseSession, MediaRoute2ProviderService.this, sessionId)); } diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 6d37c2df63ec..971b08d17cdb 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -57,7 +57,7 @@ public class MediaRouter2 { private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final Object sRouterLock = new Object(); - @GuardedBy("sLock") + @GuardedBy("sRouterLock") private static MediaRouter2 sInstance; private final Context mContext; @@ -73,23 +73,23 @@ public class MediaRouter2 { new CopyOnWriteArrayList<>(); private final String mPackageName; - @GuardedBy("sLock") + @GuardedBy("sRouterLock") final Map<String, MediaRoute2Info> mRoutes = new HashMap<>(); - @GuardedBy("sLock") + @GuardedBy("sRouterLock") private RouteDiscoveryPreference mDiscoveryPreference = RouteDiscoveryPreference.EMPTY; // TODO: Make MediaRouter2 is always connected to the MediaRouterService. - @GuardedBy("sLock") + @GuardedBy("sRouterLock") Client2 mClient; - @GuardedBy("sLock") + @GuardedBy("sRouterLock") private Map<String, RoutingController> mRoutingControllers = new ArrayMap<>(); private AtomicInteger mSessionCreationRequestCnt = new AtomicInteger(1); final Handler mHandler; - @GuardedBy("sLock") + @GuardedBy("sRouterLock") private boolean mShouldUpdateRoutes; private volatile List<MediaRoute2Info> mFilteredRoutes = Collections.emptyList(); @@ -284,26 +284,20 @@ public class MediaRouter2 { * Requests the media route provider service to create a session with the given route. * * @param route the route you want to create a session with. - * @param routeFeature the route feature of the session. Should not be empty. * * @see SessionCallback#onSessionCreated * @see SessionCallback#onSessionCreationFailed * @hide */ @NonNull - public void requestCreateSession(@NonNull MediaRoute2Info route, - @NonNull String routeFeature) { + public void requestCreateSession(@NonNull MediaRoute2Info route) { Objects.requireNonNull(route, "route must not be null"); - if (TextUtils.isEmpty(routeFeature)) { - throw new IllegalArgumentException("routeFeature must not be empty"); - } // TODO: Check the given route exists - // TODO: Check the route supports the given routeFeature final int requestId; requestId = mSessionCreationRequestCnt.getAndIncrement(); - SessionCreationRequest request = new SessionCreationRequest(requestId, route, routeFeature); + SessionCreationRequest request = new SessionCreationRequest(requestId, route); mSessionCreationRequests.add(request); Client2 client; @@ -312,7 +306,7 @@ public class MediaRouter2 { } if (client != null) { try { - mMediaRouterService.requestCreateSession(client, route, routeFeature, requestId); + mMediaRouterService.requestCreateSession(client, route, requestId); } catch (RemoteException ex) { Log.e(TAG, "Unable to request to create session.", ex); mHandler.sendMessage(obtainMessage(MediaRouter2::createControllerOnHandler, @@ -468,27 +462,18 @@ public class MediaRouter2 { mSessionCreationRequests.remove(matchingRequest); MediaRoute2Info requestedRoute = matchingRequest.mRoute; - String requestedRouteFeature = matchingRequest.mRouteFeature; if (sessionInfo == null) { // TODO: We may need to distinguish between failure and rejection. // One way can be introducing 'reason'. - notifySessionCreationFailed(requestedRoute, requestedRouteFeature); - return; - } else if (!TextUtils.equals(requestedRouteFeature, - sessionInfo.getRouteFeature())) { - Log.w(TAG, "The session has different route feature from what we requested. " - + "(requested=" + requestedRouteFeature - + ", actual=" + sessionInfo.getRouteFeature() - + ")"); - notifySessionCreationFailed(requestedRoute, requestedRouteFeature); + notifySessionCreationFailed(requestedRoute); return; } else if (!sessionInfo.getSelectedRoutes().contains(requestedRoute.getId())) { Log.w(TAG, "The session does not contain the requested route. " + "(requestedRouteId=" + requestedRoute.getId() + ", actualRoutes=" + sessionInfo.getSelectedRoutes() + ")"); - notifySessionCreationFailed(requestedRoute, requestedRouteFeature); + notifySessionCreationFailed(requestedRoute); return; } else if (!TextUtils.equals(requestedRoute.getProviderId(), sessionInfo.getProviderId())) { @@ -496,7 +481,7 @@ public class MediaRouter2 { + "(requested route's providerId=" + requestedRoute.getProviderId() + ", actual providerId=" + sessionInfo.getProviderId() + ")"); - notifySessionCreationFailed(requestedRoute, requestedRouteFeature); + notifySessionCreationFailed(requestedRoute); return; } } @@ -617,10 +602,10 @@ public class MediaRouter2 { } } - private void notifySessionCreationFailed(MediaRoute2Info route, String routeFeature) { + private void notifySessionCreationFailed(MediaRoute2Info route) { for (SessionCallbackRecord record: mSessionCallbackRecords) { record.mExecutor.execute( - () -> record.mSessionCallback.onSessionCreationFailed(route, routeFeature)); + () -> record.mSessionCallback.onSessionCreationFailed(route)); } } @@ -688,10 +673,8 @@ public class MediaRouter2 { * Called when the session creation request failed. * * @param requestedRoute the route info which was used for the request - * @param requestedRouteFeature the route feature which was used for the request */ - public void onSessionCreationFailed(@NonNull MediaRoute2Info requestedRoute, - @NonNull String requestedRouteFeature) {} + public void onSessionCreationFailed(@NonNull MediaRoute2Info requestedRoute) {} /** * Called when the session info has changed. @@ -728,16 +711,15 @@ public class MediaRouter2 { * For example, selecting/deselcting/transferring routes to session can be done through this * class. Instances are created by {@link MediaRouter2}. * - * TODO: Need to add toString() * @hide */ public final class RoutingController { private final Object mControllerLock = new Object(); - @GuardedBy("mLock") + @GuardedBy("mControllerLock") private RoutingSessionInfo mSessionInfo; - @GuardedBy("mLock") + @GuardedBy("mControllerLock") private volatile boolean mIsReleased; RoutingController(@NonNull RoutingSessionInfo sessionInfo) { @@ -754,16 +736,6 @@ public class MediaRouter2 { } /** - * @return the feature which is used by the session mainly. - */ - @NonNull - public String getRouteFeature() { - synchronized (mControllerLock) { - return mSessionInfo.getRouteFeature(); - } - } - - /** * @return the control hints used to control routing session if available. */ @Nullable @@ -998,6 +970,37 @@ public class MediaRouter2 { } } + @Override + public String toString() { + // To prevent logging spam, we only print the ID of each route. + List<String> selectedRoutes = getSelectedRoutes().stream() + .map(MediaRoute2Info::getId).collect(Collectors.toList()); + List<String> selectableRoutes = getSelectableRoutes().stream() + .map(MediaRoute2Info::getId).collect(Collectors.toList()); + List<String> deselectableRoutes = getDeselectableRoutes().stream() + .map(MediaRoute2Info::getId).collect(Collectors.toList()); + List<String> transferrableRoutes = getTransferrableRoutes().stream() + .map(MediaRoute2Info::getId).collect(Collectors.toList()); + + StringBuilder result = new StringBuilder() + .append("RoutingController{ ") + .append("sessionId=").append(getSessionId()) + .append(", selectedRoutes={") + .append(selectedRoutes) + .append("}") + .append(", selectableRoutes={") + .append(selectableRoutes) + .append("}") + .append(", deselectableRoutes={") + .append(deselectableRoutes) + .append("}") + .append(", transferrableRoutes={") + .append(transferrableRoutes) + .append("}") + .append(" }"); + return result.toString(); + } + /** * TODO: Change this to package private. (Hidden for debugging purposes) * @hide @@ -1091,13 +1094,10 @@ public class MediaRouter2 { final class SessionCreationRequest { public final MediaRoute2Info mRoute; - public final String mRouteFeature; public final int mRequestId; - SessionCreationRequest(int requestId, @NonNull MediaRoute2Info route, - @NonNull String routeFeature) { + SessionCreationRequest(int requestId, @NonNull MediaRoute2Info route) { mRoute = route; - mRouteFeature = routeFeature; mRequestId = requestId; } } diff --git a/media/java/android/media/MediaTranscodeManager.java b/media/java/android/media/MediaTranscodeManager.java new file mode 100644 index 000000000000..2c431b98fc7a --- /dev/null +++ b/media/java/android/media/MediaTranscodeManager.java @@ -0,0 +1,403 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +import android.annotation.CallbackExecutor; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.net.Uri; +import android.util.Log; + +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Executor; +import java.util.concurrent.locks.ReentrantLock; + +/** + * MediaTranscodeManager provides an interface to the system's media transcode service. + * Transcode requests are put in a queue and processed in order. When a transcode operation is + * completed the caller is notified via its OnTranscodingFinishedListener. In the meantime the + * caller may use the returned TranscodingJob object to cancel or check the status of a specific + * transcode operation. + * The currently supported media types are video and still images. + * + * TODO(lnilsson): Add sample code when API is settled. + * + * @hide + */ +public final class MediaTranscodeManager { + private static final String TAG = "MediaTranscodeManager"; + + // Invalid ID passed from native means the request was never enqueued. + private static final long ID_INVALID = -1; + + // Events passed from native. + private static final int EVENT_JOB_STARTED = 1; + private static final int EVENT_JOB_PROGRESSED = 2; + private static final int EVENT_JOB_FINISHED = 3; + + @IntDef(prefix = { "EVENT_" }, value = { + EVENT_JOB_STARTED, + EVENT_JOB_PROGRESSED, + EVENT_JOB_FINISHED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Event {} + + private static MediaTranscodeManager sMediaTranscodeManager; + private final ConcurrentMap<Long, TranscodingJob> mPendingTranscodingJobs = + new ConcurrentHashMap<>(); + private final Context mContext; + + /** + * Listener that gets notified when a transcoding operation has finished. + * This listener gets notified regardless of how the operation finished. It is up to the + * listener implementation to check the result and take appropriate action. + */ + @FunctionalInterface + public interface OnTranscodingFinishedListener { + /** + * Called when the transcoding operation has finished. The receiver may use the + * TranscodingJob to check the result, i.e. whether the operation succeeded, was canceled or + * if an error occurred. + * @param transcodingJob The TranscodingJob instance for the finished transcoding operation. + */ + void onTranscodingFinished(@NonNull TranscodingJob transcodingJob); + } + + /** + * Class describing a transcode operation to be performed. The caller uses this class to + * configure a transcoding operation that can then be enqueued using MediaTranscodeManager. + */ + public static final class TranscodingRequest { + private Uri mSrcUri; + private Uri mDstUri; + private MediaFormat mDstFormat; + + private TranscodingRequest(Builder b) { + mSrcUri = b.mSrcUri; + mDstUri = b.mDstUri; + mDstFormat = b.mDstFormat; + } + + /** TranscodingRequest builder class. */ + public static class Builder { + private Uri mSrcUri; + private Uri mDstUri; + private MediaFormat mDstFormat; + + /** + * Specifies the source media file. + * @param uri Content uri for the source media file. + * @return The builder instance. + */ + public Builder setSourceUri(Uri uri) { + mSrcUri = uri; + return this; + } + + /** + * Specifies the destination media file. + * @param uri Content uri for the destination media file. + * @return The builder instance. + */ + public Builder setDestinationUri(Uri uri) { + mDstUri = uri; + return this; + } + + /** + * Specifies the media format of the transcoded media file. + * @param dstFormat MediaFormat containing the desired destination format. + * @return The builder instance. + */ + public Builder setDestinationFormat(MediaFormat dstFormat) { + mDstFormat = dstFormat; + return this; + } + + /** + * Builds a new TranscodingRequest with the configuration set on this builder. + * @return A new TranscodingRequest. + */ + public TranscodingRequest build() { + return new TranscodingRequest(this); + } + } + } + + /** + * Handle to an enqueued transcoding operation. An instance of this class represents a single + * enqueued transcoding operation. The caller can use that instance to query the status or + * progress, and to get the result once the operation has completed. + */ + public static final class TranscodingJob { + /** The job is enqueued but not yet running. */ + public static final int STATUS_PENDING = 1; + /** The job is currently running. */ + public static final int STATUS_RUNNING = 2; + /** The job is finished. */ + public static final int STATUS_FINISHED = 3; + + @IntDef(prefix = { "STATUS_" }, value = { + STATUS_PENDING, + STATUS_RUNNING, + STATUS_FINISHED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Status {} + + /** The job does not have a result yet. */ + public static final int RESULT_NONE = 1; + /** The job completed successfully. */ + public static final int RESULT_SUCCESS = 2; + /** The job encountered an error while running. */ + public static final int RESULT_ERROR = 3; + /** The job was canceled by the caller. */ + public static final int RESULT_CANCELED = 4; + + @IntDef(prefix = { "RESULT_" }, value = { + RESULT_NONE, + RESULT_SUCCESS, + RESULT_ERROR, + RESULT_CANCELED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Result {} + + /** Listener that gets notified when the progress changes. */ + @FunctionalInterface + public interface OnProgressChangedListener { + + /** + * Called when the progress changes. The progress is between 0 and 1, where 0 means + * that the job has not yet started and 1 means that it has finished. + * @param progress The new progress. + */ + void onProgressChanged(float progress); + } + + private final Executor mExecutor; + private final OnTranscodingFinishedListener mListener; + private final ReentrantLock mStatusChangeLock = new ReentrantLock(); + private Executor mProgressChangedExecutor; + private OnProgressChangedListener mProgressChangedListener; + private long mID; + private float mProgress = 0.0f; + private @Status int mStatus = STATUS_PENDING; + private @Result int mResult = RESULT_NONE; + + private TranscodingJob(long id, @NonNull @CallbackExecutor Executor executor, + @NonNull OnTranscodingFinishedListener listener) { + mID = id; + mExecutor = executor; + mListener = listener; + } + + /** + * Set a progress listener. + * @param listener The progress listener. + */ + public void setOnProgressChangedListener(@NonNull @CallbackExecutor Executor executor, + @Nullable OnProgressChangedListener listener) { + mProgressChangedExecutor = executor; + mProgressChangedListener = listener; + } + + /** + * Cancels the transcoding job and notify the listener. If the job happened to finish before + * being canceled this call is effectively a no-op and will not update the result in that + * case. + */ + public void cancel() { + setJobFinished(RESULT_CANCELED); + sMediaTranscodeManager.native_cancelTranscodingRequest(mID); + } + + /** + * Gets the progress of the transcoding job. The progress is between 0 and 1, where 0 means + * that the job has not yet started and 1 means that it is finished. + * @return The progress. + */ + public float getProgress() { + return mProgress; + } + + /** + * Gets the status of the transcoding job. + * @return The status. + */ + public @Status int getStatus() { + return mStatus; + } + + /** + * Gets the result of the transcoding job. + * @return The result. + */ + public @Result int getResult() { + return mResult; + } + + private void setJobStarted() { + mStatus = STATUS_RUNNING; + } + + private void setJobProgress(float newProgress) { + mProgress = newProgress; + + // Notify listener. + OnProgressChangedListener onProgressChangedListener = mProgressChangedListener; + if (onProgressChangedListener != null) { + mProgressChangedExecutor.execute( + () -> onProgressChangedListener.onProgressChanged(mProgress)); + } + } + + private void setJobFinished(int result) { + boolean doNotifyListener = false; + + // Prevent conflicting simultaneous status updates from native (finished) and from the + // caller (cancel). + try { + mStatusChangeLock.lock(); + if (mStatus != STATUS_FINISHED) { + mStatus = STATUS_FINISHED; + mResult = result; + doNotifyListener = true; + } + } finally { + mStatusChangeLock.unlock(); + } + + if (doNotifyListener) { + mExecutor.execute(() -> mListener.onTranscodingFinished(this)); + } + } + + private void processJobEvent(@Event int event, int arg) { + switch (event) { + case EVENT_JOB_STARTED: + setJobStarted(); + break; + case EVENT_JOB_PROGRESSED: + setJobProgress((float) arg / 100); + break; + case EVENT_JOB_FINISHED: + setJobFinished(arg); + break; + default: + Log.e(TAG, "Unsupported event: " + event); + break; + } + } + } + + // Initializes the native library. + private static native void native_init(); + // Requests a new job ID from the native service. + private native long native_requestUniqueJobID(); + // Enqueues a transcoding request to the native service. + private native boolean native_enqueueTranscodingRequest( + long id, @NonNull TranscodingRequest transcodingRequest, @NonNull Context context); + // Cancels an enqueued transcoding request. + private native void native_cancelTranscodingRequest(long id); + + // Private constructor. + private MediaTranscodeManager(@NonNull Context context) { + mContext = context; + } + + // Events posted from the native service. + @SuppressWarnings("unused") + private void postEventFromNative(@Event int event, long id, int arg) { + Log.d(TAG, String.format("postEventFromNative. Event %d, ID %d, arg %d", event, id, arg)); + + TranscodingJob transcodingJob = mPendingTranscodingJobs.get(id); + + // Job IDs are added to the tracking set before the job is enqueued so it should never + // be null unless the service misbehaves. + if (transcodingJob == null) { + Log.e(TAG, "No matching transcode job found for id " + id); + return; + } + + transcodingJob.processJobEvent(event, arg); + } + + /** + * Gets the MediaTranscodeManager singleton instance. + * @param context The application context. + * @return the {@link MediaTranscodeManager} singleton instance. + */ + public static MediaTranscodeManager getInstance(@NonNull Context context) { + Preconditions.checkNotNull(context); + synchronized (MediaTranscodeManager.class) { + if (sMediaTranscodeManager == null) { + sMediaTranscodeManager = new MediaTranscodeManager(context.getApplicationContext()); + } + return sMediaTranscodeManager; + } + } + + /** + * Enqueues a TranscodingRequest for execution. + * @param transcodingRequest The TranscodingRequest to enqueue. + * @param listenerExecutor Executor on which the listener is notified. + * @param listener Listener to get notified when the transcoding job is finished. + * @return A TranscodingJob for this operation. + */ + public @Nullable TranscodingJob enqueueTranscodingRequest( + @NonNull TranscodingRequest transcodingRequest, + @NonNull @CallbackExecutor Executor listenerExecutor, + @NonNull OnTranscodingFinishedListener listener) { + Log.i(TAG, "enqueueTranscodingRequest called."); + Preconditions.checkNotNull(transcodingRequest); + Preconditions.checkNotNull(listenerExecutor); + Preconditions.checkNotNull(listener); + + // Reserve a job ID. + long jobID = native_requestUniqueJobID(); + if (jobID == ID_INVALID) { + return null; + } + + // Add the job to the tracking set. + TranscodingJob transcodingJob = new TranscodingJob(jobID, listenerExecutor, listener); + mPendingTranscodingJobs.put(jobID, transcodingJob); + + // Enqueue the request with the native service. + boolean enqueued = native_enqueueTranscodingRequest(jobID, transcodingRequest, mContext); + if (!enqueued) { + mPendingTranscodingJobs.remove(jobID); + return null; + } + + return transcodingJob; + } + + static { + System.loadLibrary("media_jni"); + native_init(); + } +} diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java index 96acf6cec5b4..228addebe029 100644 --- a/media/java/android/media/RoutingSessionInfo.java +++ b/media/java/android/media/RoutingSessionInfo.java @@ -51,7 +51,6 @@ public final class RoutingSessionInfo implements Parcelable { final String mId; final String mClientPackageName; - final String mRouteFeature; @Nullable final String mProviderId; final List<String> mSelectedRoutes; @@ -66,7 +65,6 @@ public final class RoutingSessionInfo implements Parcelable { mId = builder.mId; mClientPackageName = builder.mClientPackageName; - mRouteFeature = builder.mRouteFeature; mProviderId = builder.mProviderId; // TODO: Needs to check that the routes already have unique IDs. @@ -87,7 +85,6 @@ public final class RoutingSessionInfo implements Parcelable { mId = ensureString(src.readString()); mClientPackageName = ensureString(src.readString()); - mRouteFeature = ensureString(src.readString()); mProviderId = src.readString(); mSelectedRoutes = ensureList(src.createStringArrayList()); @@ -119,7 +116,7 @@ public final class RoutingSessionInfo implements Parcelable { * In order to ensure uniqueness in {@link MediaRouter2} side, the value of this method * can be different from what was set in {@link MediaRoute2ProviderService}. * - * @see Builder#Builder(String, String, String) + * @see Builder#Builder(String, String) */ @NonNull public String getId() { @@ -131,7 +128,7 @@ public final class RoutingSessionInfo implements Parcelable { } /** - * Gets the original id set by {@link Builder#Builder(String, String, String)}. + * Gets the original id set by {@link Builder#Builder(String, String)}. * @hide */ @NonNull @@ -148,15 +145,6 @@ public final class RoutingSessionInfo implements Parcelable { } /** - * Gets the route feature of the session. - * Routes that don't have the feature can't be selected into the session. - */ - @NonNull - public String getRouteFeature() { - return mRouteFeature; - } - - /** * Gets the provider id of the session. * @hide */ @@ -214,7 +202,6 @@ public final class RoutingSessionInfo implements Parcelable { public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeString(mId); dest.writeString(mClientPackageName); - dest.writeString(mRouteFeature); dest.writeString(mProviderId); dest.writeStringList(mSelectedRoutes); dest.writeStringList(mSelectableRoutes); @@ -235,7 +222,6 @@ public final class RoutingSessionInfo implements Parcelable { RoutingSessionInfo other = (RoutingSessionInfo) obj; return Objects.equals(mId, other.mId) && Objects.equals(mClientPackageName, other.mClientPackageName) - && Objects.equals(mRouteFeature, other.mRouteFeature) && Objects.equals(mProviderId, other.mProviderId) && Objects.equals(mSelectedRoutes, other.mSelectedRoutes) && Objects.equals(mSelectableRoutes, other.mSelectableRoutes) @@ -245,7 +231,7 @@ public final class RoutingSessionInfo implements Parcelable { @Override public int hashCode() { - return Objects.hash(mId, mClientPackageName, mRouteFeature, mProviderId, + return Objects.hash(mId, mClientPackageName, mProviderId, mSelectedRoutes, mSelectableRoutes, mDeselectableRoutes, mTransferrableRoutes); } @@ -254,7 +240,6 @@ public final class RoutingSessionInfo implements Parcelable { StringBuilder result = new StringBuilder() .append("RoutingSessionInfo{ ") .append("sessionId=").append(mId) - .append(", routeFeature=").append(mRouteFeature) .append(", selectedRoutes={") .append(String.join(",", mSelectedRoutes)) .append("}") @@ -295,7 +280,6 @@ public final class RoutingSessionInfo implements Parcelable { public static final class Builder { final String mId; final String mClientPackageName; - final String mRouteFeature; String mProviderId; final List<String> mSelectedRoutes; final List<String> mSelectableRoutes; @@ -314,22 +298,16 @@ public final class RoutingSessionInfo implements Parcelable { * @param id ID of the session. Must not be empty. * @param clientPackageName package name of the client app which uses this session. * If is is unknown, then just use an empty string. - * @param routeFeature the route feature of session. Must not be empty. * @see MediaRoute2Info#getId() */ - public Builder(@NonNull String id, @NonNull String clientPackageName, - @NonNull String routeFeature) { + public Builder(@NonNull String id, @NonNull String clientPackageName) { if (TextUtils.isEmpty(id)) { throw new IllegalArgumentException("id must not be empty"); } Objects.requireNonNull(clientPackageName, "clientPackageName must not be null"); - if (TextUtils.isEmpty(routeFeature)) { - throw new IllegalArgumentException("routeFeature must not be empty"); - } mId = id; mClientPackageName = clientPackageName; - mRouteFeature = routeFeature; mSelectedRoutes = new ArrayList<>(); mSelectableRoutes = new ArrayList<>(); mDeselectableRoutes = new ArrayList<>(); @@ -347,7 +325,6 @@ public final class RoutingSessionInfo implements Parcelable { mId = sessionInfo.mId; mClientPackageName = sessionInfo.mClientPackageName; - mRouteFeature = sessionInfo.mRouteFeature; mProviderId = sessionInfo.mProviderId; mSelectedRoutes = new ArrayList<>(sessionInfo.mSelectedRoutes); diff --git a/media/java/android/media/VolumeProvider.java b/media/java/android/media/VolumeProvider.java index 8f68cbda3e3c..ed272d54dac8 100644 --- a/media/java/android/media/VolumeProvider.java +++ b/media/java/android/media/VolumeProvider.java @@ -16,6 +16,7 @@ package android.media; import android.annotation.IntDef; +import android.annotation.Nullable; import android.media.session.MediaSession; import java.lang.annotation.Retention; @@ -60,6 +61,7 @@ public abstract class VolumeProvider { private final int mControlType; private final int mMaxVolume; + private final String mControlId; private int mCurrentVolume; private Callback mCallback; @@ -73,10 +75,28 @@ public abstract class VolumeProvider { * @param maxVolume The maximum allowed volume. * @param currentVolume The current volume on the output. */ + public VolumeProvider(@ControlType int volumeControl, int maxVolume, int currentVolume) { + this(volumeControl, maxVolume, currentVolume, null); + } + + /** + * Create a new volume provider for handling volume events. You must specify + * the type of volume control, the maximum volume that can be used, and the + * current volume on the output. + * + * @param volumeControl The method for controlling volume that is used by + * this provider. + * @param maxVolume The maximum allowed volume. + * @param currentVolume The current volume on the output. + * @param volumeControlId The volume control id of this provider. + */ + public VolumeProvider(@ControlType int volumeControl, int maxVolume, int currentVolume, + @Nullable String volumeControlId) { mControlType = volumeControl; mMaxVolume = maxVolume; mCurrentVolume = currentVolume; + mControlId = volumeControlId; } /** @@ -122,6 +142,17 @@ public abstract class VolumeProvider { } /** + * Gets the volume control id. It can be used to identify which volume provider is + * used by the session. + * + * @return the volume control id or {@code null} if it isn't set. + */ + @Nullable + public final String getVolumeControlId() { + return mControlId; + } + + /** * Override to handle requests to set the volume of the current output. * After the volume has been modified {@link #setCurrentVolume} must be * called to notify the system. diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java index 01f12500b77b..27f02fe528f3 100644 --- a/media/java/android/media/audiopolicy/AudioPolicy.java +++ b/media/java/android/media/audiopolicy/AudioPolicy.java @@ -719,9 +719,14 @@ public class AudioPolicy { if (track == null) { break; } - // TODO: add synchronous versions - track.stop(); - track.flush(); + try { + // TODO: add synchronous versions + track.stop(); + track.flush(); + } catch (IllegalStateException e) { + // ignore exception, AudioTrack could have already been stopped or + // released by the user of the AudioPolicy + } } } if (mCaptors != null) { @@ -730,8 +735,13 @@ public class AudioPolicy { if (record == null) { break; } - // TODO: if needed: implement an invalidate method - record.stop(); + try { + // TODO: if needed: implement an invalidate method + record.stop(); + } catch (IllegalStateException e) { + // ignore exception, AudioRecord could have already been stopped or + // released by the user of the AudioPolicy + } } } } diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl index 4d68a6ae52e4..21378c80fd56 100644 --- a/media/java/android/media/session/ISession.aidl +++ b/media/java/android/media/session/ISession.aidl @@ -48,6 +48,6 @@ interface ISession { // These commands relate to volume handling void setPlaybackToLocal(in AudioAttributes attributes); - void setPlaybackToRemote(int control, int max); + void setPlaybackToRemote(int control, int max, @nullable String controlId); void setCurrentVolume(int currentVolume); } diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java index 1812d9ce0739..c2f620608b20 100644 --- a/media/java/android/media/session/MediaController.java +++ b/media/java/android/media/session/MediaController.java @@ -964,16 +964,26 @@ public final class MediaController { private final int mMaxVolume; private final int mCurrentVolume; private final AudioAttributes mAudioAttrs; + private final String mVolumeControlId; /** * @hide */ public PlaybackInfo(int type, int control, int max, int current, AudioAttributes attrs) { + this(type, control, max, current, attrs, null); + } + + /** + * @hide + */ + public PlaybackInfo(int type, int control, int max, int current, AudioAttributes attrs, + String volumeControlId) { mVolumeType = type; mVolumeControl = control; mMaxVolume = max; mCurrentVolume = current; mAudioAttrs = attrs; + mVolumeControlId = volumeControlId; } PlaybackInfo(Parcel in) { @@ -982,6 +992,7 @@ public final class MediaController { mMaxVolume = in.readInt(); mCurrentVolume = in.readInt(); mAudioAttrs = in.readParcelable(null); + mVolumeControlId = in.readString(); } /** @@ -1042,11 +1053,24 @@ public final class MediaController { return mAudioAttrs; } + /** + * Gets the volume control ID for this session. It can be used to identify which + * volume provider is used by the session. + * + * @return the volume control ID for this session or {@code null} if it's local playback + * or not set. + * @see VolumeProvider#getVolumeControlId() + */ + @Nullable + public String getVolumeControlId() { + return mVolumeControlId; + } + @Override public String toString() { return "volumeType=" + mVolumeType + ", volumeControl=" + mVolumeControl + ", maxVolume=" + mMaxVolume + ", currentVolume=" + mCurrentVolume - + ", audioAttrs=" + mAudioAttrs; + + ", audioAttrs=" + mAudioAttrs + ", volumeControlId=" + mVolumeControlId; } @Override @@ -1061,6 +1085,7 @@ public final class MediaController { dest.writeInt(mMaxVolume); dest.writeInt(mCurrentVolume); dest.writeParcelable(mAudioAttrs, flags); + dest.writeString(mVolumeControlId); } public static final @android.annotation.NonNull Parcelable.Creator<PlaybackInfo> CREATOR = diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java index 3adee590c125..9953626f0a7a 100644 --- a/media/java/android/media/session/MediaSession.java +++ b/media/java/android/media/session/MediaSession.java @@ -335,7 +335,7 @@ public final class MediaSession { try { mBinder.setPlaybackToRemote(volumeProvider.getVolumeControl(), - volumeProvider.getMaxVolume()); + volumeProvider.getMaxVolume(), volumeProvider.getVolumeControlId()); mBinder.setCurrentVolume(volumeProvider.getCurrentVolume()); } catch (RemoteException e) { Log.wtf(TAG, "Failure in setPlaybackToRemote.", e); diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java index 938ffcd5f731..dd4dac223a86 100644 --- a/media/java/android/media/soundtrigger/SoundTriggerManager.java +++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java @@ -44,6 +44,7 @@ import com.android.internal.app.ISoundTriggerService; import com.android.internal.util.Preconditions; import java.util.HashMap; +import java.util.Objects; import java.util.UUID; /** @@ -175,19 +176,40 @@ public final class SoundTriggerManager { * Factory constructor to create a SoundModel instance for use with methods in this * class. */ - public static Model create(UUID modelUuid, UUID vendorUuid, byte[] data) { - return new Model(new SoundTrigger.GenericSoundModel(modelUuid, - vendorUuid, data)); + @NonNull + public static Model create(@NonNull UUID modelUuid, @NonNull UUID vendorUuid, + @Nullable byte[] data, int version) { + Objects.requireNonNull(modelUuid); + Objects.requireNonNull(vendorUuid); + return new Model(new SoundTrigger.GenericSoundModel(modelUuid, vendorUuid, data, + version)); } + /** + * Factory constructor to create a SoundModel instance for use with methods in this + * class. + */ + @NonNull + public static Model create(@NonNull UUID modelUuid, @NonNull UUID vendorUuid, + @Nullable byte[] data) { + return create(modelUuid, vendorUuid, data, -1); + } + + @NonNull public UUID getModelUuid() { return mGenericSoundModel.uuid; } + @NonNull public UUID getVendorUuid() { return mGenericSoundModel.vendorUuid; } + public int getVersion() { + return mGenericSoundModel.version; + } + + @Nullable public byte[] getModelData() { return mGenericSoundModel.data; } diff --git a/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl b/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl index 149c1cd0447b..726af7681979 100644 --- a/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl +++ b/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl @@ -44,4 +44,11 @@ oneway interface ISoundTriggerCallback { * and this event will be sent in addition to the abort event. */ void onRecognitionAvailabilityChange(boolean available); + /** + * Notifies the client that the associated module has crashed and restarted. The module instance + * is no longer usable and will throw a ServiceSpecificException with a Status.DEAD_OBJECT code + * for every call. The client should detach, then re-attach to the module in order to get a new, + * usable instance. All state for this module has been lost. + */ + void onModuleDied(); } diff --git a/media/java/android/media/soundtrigger_middleware/Status.aidl b/media/java/android/media/soundtrigger_middleware/Status.aidl index 5d082e272295..85ccacf21c8d 100644 --- a/media/java/android/media/soundtrigger_middleware/Status.aidl +++ b/media/java/android/media/soundtrigger_middleware/Status.aidl @@ -28,4 +28,6 @@ enum Status { OPERATION_NOT_SUPPORTED = 2, /** Temporary lack of permission. */ TEMPORARY_PERMISSION_DENIED = 3, + /** The object on which this method is called is dead and all of its state is lost. */ + DEAD_OBJECT = 4, } diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl index b076bb67738f..b5e9d1b2939f 100644 --- a/media/java/android/media/tv/ITvInputManager.aidl +++ b/media/java/android/media/tv/ITvInputManager.aidl @@ -60,6 +60,7 @@ interface ITvInputManager { void createSession(in ITvInputClient client, in String inputId, boolean isRecordingSession, int seq, int userId); void releaseSession(in IBinder sessionToken, int userId); + int getClientPid(in String sessionId); void setMainSession(in IBinder sessionToken, int userId); void setSurface(in IBinder sessionToken, in Surface surface, int userId); diff --git a/media/java/android/media/tv/ITvInputService.aidl b/media/java/android/media/tv/ITvInputService.aidl index f90c50491129..8ccf13ae2d72 100755 --- a/media/java/android/media/tv/ITvInputService.aidl +++ b/media/java/android/media/tv/ITvInputService.aidl @@ -30,8 +30,9 @@ oneway interface ITvInputService { void registerCallback(in ITvInputServiceCallback callback); void unregisterCallback(in ITvInputServiceCallback callback); void createSession(in InputChannel channel, in ITvInputSessionCallback callback, - in String inputId); - void createRecordingSession(in ITvInputSessionCallback callback, in String inputId); + in String inputId, in String sessionId); + void createRecordingSession(in ITvInputSessionCallback callback, in String inputId, + in String sessionId); // For hardware TvInputService void notifyHardwareAdded(in TvInputHardwareInfo hardwareInfo); diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index 854ea43f17c3..630d8191eff0 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -266,6 +266,15 @@ public final class TvInputManager { public static final int INPUT_STATE_DISCONNECTED = 2; /** + * An unknown state of the client pid gets from the TvInputManager. Client gets this value when + * query through {@link getClientPid(String sessionId)} fails. + * + * @hide + */ + @SystemApi + public static final int UNKNOWN_CLIENT_PID = -1; + + /** * Broadcast intent action when the user blocked content ratings change. For use with the * {@link #isRatingBlocked}. */ @@ -1484,6 +1493,21 @@ public final class TvInputManager { } /** + * Get a the client pid when creating the session with the session id provided. + * + * @param sessionId a String of session id that is used to query the client pid. + * @return the client pid when created the session. Returns {@link #UNKNOWN_CLIENT_PID} + * if the call fails. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS) + public int getClientPid(@NonNull String sessionId) { + return getClientPidInternal(sessionId); + }; + + /** * Creates a recording {@link Session} for a given TV input. * * <p>The number of sessions that can be created at the same time is limited by the capability @@ -1516,6 +1540,17 @@ public final class TvInputManager { } } + private int getClientPidInternal(String sessionId) { + Preconditions.checkNotNull(sessionId); + int clientPid = UNKNOWN_CLIENT_PID; + try { + clientPid = mService.getClientPid(sessionId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return clientPid; + } + /** * Returns the TvStreamConfig list of the given TV input. * diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 7fbb3376d5fb..629dc7ce7819 100755 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -124,7 +124,7 @@ public abstract class TvInputService extends Service { @Override public void createSession(InputChannel channel, ITvInputSessionCallback cb, - String inputId) { + String inputId, String sessionId) { if (channel == null) { Log.w(TAG, "Creating session without input channel"); } @@ -135,17 +135,20 @@ public abstract class TvInputService extends Service { args.arg1 = channel; args.arg2 = cb; args.arg3 = inputId; + args.arg4 = sessionId; mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args).sendToTarget(); } @Override - public void createRecordingSession(ITvInputSessionCallback cb, String inputId) { + public void createRecordingSession(ITvInputSessionCallback cb, String inputId, + String sessionId) { if (cb == null) { return; } SomeArgs args = SomeArgs.obtain(); args.arg1 = cb; args.arg2 = inputId; + args.arg3 = sessionId; mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_RECORDING_SESSION, args) .sendToTarget(); } @@ -208,6 +211,37 @@ public abstract class TvInputService extends Service { } /** + * Returns a concrete implementation of {@link Session}. + * + * <p>For any apps that needs sessionId to request tuner resources from TunerResourceManager, + * it needs to override this method to get the sessionId passed. When no overriding, this method + * calls {@link #onCreateSession(String)} defaultly. + * + * @param inputId The ID of the TV input associated with the session. + * @param sessionId the unique sessionId created by TIF when session is created. + */ + @Nullable + public Session onCreateSession(@NonNull String inputId, @NonNull String sessionId) { + return onCreateSession(inputId); + } + + /** + * Returns a concrete implementation of {@link RecordingSession}. + * + * <p>For any apps that needs sessionId to request tuner resources from TunerResourceManager, + * it needs to override this method to get the sessionId passed. When no overriding, this method + * calls {@link #onCreateRecordingSession(String)} defaultly. + * + * @param inputId The ID of the TV input associated with the recording session. + * @param sessionId the unique sessionId created by TIF when session is created. + */ + @Nullable + public RecordingSession onCreateRecordingSession( + @NonNull String inputId, @NonNull String sessionId) { + return onCreateRecordingSession(inputId); + } + + /** * Returns a new {@link TvInputInfo} object if this service is responsible for * {@code hardwareInfo}; otherwise, return {@code null}. Override to modify default behavior of * ignoring all hardware input. @@ -2032,8 +2066,9 @@ public abstract class TvInputService extends Service { InputChannel channel = (InputChannel) args.arg1; ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2; String inputId = (String) args.arg3; + String sessionId = (String) args.arg4; args.recycle(); - Session sessionImpl = onCreateSession(inputId); + Session sessionImpl = onCreateSession(inputId, sessionId); if (sessionImpl == null) { try { // Failed to create a session. @@ -2103,8 +2138,10 @@ public abstract class TvInputService extends Service { SomeArgs args = (SomeArgs) msg.obj; ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg1; String inputId = (String) args.arg2; + String sessionId = (String) args.arg3; args.recycle(); - RecordingSession recordingSessionImpl = onCreateRecordingSession(inputId); + RecordingSession recordingSessionImpl = + onCreateRecordingSession(inputId, sessionId); if (recordingSessionImpl == null) { try { // Failed to create a recording session. diff --git a/media/java/android/media/tv/tuner/Lnb.java b/media/java/android/media/tv/tuner/Lnb.java index c7cc9e6ddb7f..8e579bf897dd 100644 --- a/media/java/android/media/tv/tuner/Lnb.java +++ b/media/java/android/media/tv/tuner/Lnb.java @@ -23,7 +23,6 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.content.Context; import android.hardware.tv.tuner.V1_0.Constants; -import android.media.tv.tuner.Tuner.LnbCallback; import android.media.tv.tuner.TunerConstants.Result; import java.lang.annotation.Retention; diff --git a/media/java/android/media/tv/tuner/LnbCallback.java b/media/java/android/media/tv/tuner/LnbCallback.java new file mode 100644 index 000000000000..99bbf866aa69 --- /dev/null +++ b/media/java/android/media/tv/tuner/LnbCallback.java @@ -0,0 +1,39 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv.tuner; + + +/** + * Callback interface for receiving information from LNBs. + * + * @hide + */ +public interface LnbCallback { + /** + * Invoked when there is a LNB event. + */ + void onEvent(int lnbEventType); + + /** + * Invoked when there is a new DiSEqC message. + * + * @param diseqcMessage a byte array of data for DiSEqC (Digital Satellite + * Equipment Control) message which is specified by EUTELSAT Bus Functional + * Specification Version 4.2. + */ + void onDiseqcMessage(byte[] diseqcMessage); +} diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index 862489f4ed8d..18969ae3279c 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -16,6 +16,8 @@ package android.media.tv.tuner; +import android.annotation.BytesLong; +import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -26,17 +28,21 @@ import android.media.tv.tuner.TunerConstants.FilterSubtype; import android.media.tv.tuner.TunerConstants.FrontendScanType; import android.media.tv.tuner.TunerConstants.Result; import android.media.tv.tuner.dvr.Dvr; +import android.media.tv.tuner.dvr.DvrCallback; +import android.media.tv.tuner.dvr.DvrSettings; import android.media.tv.tuner.filter.FilterConfiguration.FilterType; import android.media.tv.tuner.filter.FilterEvent; import android.media.tv.tuner.filter.TimeFilter; import android.media.tv.tuner.frontend.FrontendCallback; import android.media.tv.tuner.frontend.FrontendInfo; import android.media.tv.tuner.frontend.FrontendStatus; +import android.media.tv.tuner.frontend.ScanCallback; import android.os.Handler; import android.os.Looper; import android.os.Message; import java.util.List; +import java.util.concurrent.Executor; /** * This class is used to interact with hardware tuners devices. @@ -70,6 +76,10 @@ public final class Tuner implements AutoCloseable { private List<Integer> mLnbIds; private Lnb mLnb; + @Nullable + private ScanCallback mScanCallback; + @Nullable + private Executor mScanCallbackExecutor; /** * Constructs a Tuner instance. @@ -81,6 +91,31 @@ public final class Tuner implements AutoCloseable { nativeSetup(); } + /** + * Constructs a Tuner instance. + * + * @param context the context of the caller. + * @param tvInputSessionId the session ID of the TV input. + * @param useCase the use case of this Tuner instance. + * + * @hide + * TODO: replace the other constructor + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + public Tuner(@NonNull Context context, @NonNull String tvInputSessionId, int useCase) { + mContext = context; + } + + /** + * Shares the frontend resource with another Tuner instance + * + * @param tuner the Tuner instance to share frontend resource with. + * + * @hide + */ + public void shareFrontend(@NonNull Tuner tuner) { } + + private long mNativeContext; // used by native jMediaTuner /** @hide */ @@ -112,13 +147,13 @@ public final class Tuner implements AutoCloseable { private native int nativeStopScan(); private native int nativeSetLnb(int lnbId); private native int nativeSetLna(boolean enable); - private native FrontendStatus[] nativeGetFrontendStatus(int[] statusTypes); + private native FrontendStatus nativeGetFrontendStatus(int[] statusTypes); private native int nativeGetAvSyncHwId(Filter filter); private native long nativeGetAvSyncTime(int avSyncId); private native int nativeConnectCiCam(int ciCamId); private native int nativeDisconnectCiCam(); private native FrontendInfo nativeGetFrontendInfo(int id); - private native Filter nativeOpenFilter(int type, int subType, int bufferSize); + private native Filter nativeOpenFilter(int type, int subType, long bufferSize); private native TimeFilter nativeOpenTimeFilter(); private native List<Integer> nativeGetLnbIds(); @@ -126,33 +161,14 @@ public final class Tuner implements AutoCloseable { private native Descrambler nativeOpenDescrambler(); - private native Dvr nativeOpenDvr(int type, int bufferSize); + private native Dvr nativeOpenDvr(int type, long bufferSize); private static native DemuxCapabilities nativeGetDemuxCapabilities(); - /** - * LNB Callback. - * - * @hide - */ - public interface LnbCallback { - /** - * Invoked when there is a LNB event. - */ - void onEvent(int lnbEventType); - - /** - * Invoked when there is a new DiSEqC message. - * - * @param diseqcMessage a byte array of data for DiSEqC (Digital Satellite - * Equipment Control) message which is specified by EUTELSAT Bus Functional - * Specification Version 4.2. - */ - void onDiseqcMessage(byte[] diseqcMessage); - } /** * Callback interface for receiving information from the corresponding filters. + * TODO: remove */ public interface FilterCallback { /** @@ -171,20 +187,19 @@ public final class Tuner implements AutoCloseable { void onFilterStatusChanged(@NonNull Filter filter, @FilterStatus int status); } + /** - * DVR Callback. + * Listener for resource lost. * * @hide */ - public interface DvrCallback { - /** - * Invoked when record status changed. - */ - void onRecordStatus(int status); + public interface OnResourceLostListener { /** - * Invoked when playback status changed. + * Invoked when resource lost. + * + * @param tuner the tuner instance whose resource is being reclaimed. */ - void onPlaybackStatus(int status); + void onResourceLost(@NonNull Tuner tuner); } @Nullable @@ -231,29 +246,6 @@ public final class Tuner implements AutoCloseable { private Frontend(int id) { mId = id; } - - public void setCallback(@Nullable FrontendCallback callback, @Nullable Handler handler) { - mCallback = callback; - - if (mCallback == null) { - return; - } - - if (handler == null) { - // use default looper if handler is null - if (mHandler == null) { - mHandler = createEventHandler(); - } - return; - } - - Looper looper = handler.getLooper(); - if (mHandler != null && mHandler.getLooper() == looper) { - // the same looper. reuse mHandler - return; - } - mHandler = new EventHandler(looper); - } } /** @@ -272,73 +264,104 @@ public final class Tuner implements AutoCloseable { /** * Stops a previous tuning. * - * If the method completes successfully the frontend is no longer tuned and no data + * <p>If the method completes successfully, the frontend is no longer tuned and no data * will be sent to attached filters. * * @return result status of the operation. + * * @hide */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @Result public int stopTune() { + TunerUtils.checkTunerPermission(mContext); return nativeStopTune(); } /** * Scan channels. + * + * @param settings A {@link FrontendSettings} to configure the frontend. + * @param scanType The scan type. + * * @hide */ - public int scan(@NonNull FrontendSettings settings, @FrontendScanType int scanType) { + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + public int scan(@NonNull FrontendSettings settings, @FrontendScanType int scanType, + @NonNull @CallbackExecutor Executor executor, @NonNull ScanCallback scanCallback) { + TunerUtils.checkTunerPermission(mContext); + mScanCallback = scanCallback; + mScanCallbackExecutor = executor; return nativeScan(settings.getType(), settings, scanType); } /** * Stops a previous scanning. * - * If the method completes successfully, the frontend stop previous scanning. + * <p> + * The {@link ScanCallback} and it's {@link Executor} will be removed. + * + * <p> + * If the method completes successfully, the frontend stopped previous scanning. + * * @hide */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @Result public int stopScan() { - return nativeStopScan(); + TunerUtils.checkTunerPermission(mContext); + int retVal = nativeStopScan(); + mScanCallback = null; + mScanCallbackExecutor = null; + return retVal; } /** * Sets Low-Noise Block downconverter (LNB) for satellite frontend. * - * This assigns a hardware LNB resource to the satellite tuner. It can be + * <p>This assigns a hardware LNB resource to the satellite tuner. It can be * called multiple times to update LNB assignment. * * @param lnb the LNB instance. * * @return result status of the operation. + * * @hide */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @Result public int setLnb(@NonNull Lnb lnb) { + TunerUtils.checkTunerPermission(mContext); return nativeSetLnb(lnb.mId); } /** * Enable or Disable Low Noise Amplifier (LNA). * - * @param enable true to activate LNA module; false to deactivate LNA + * @param enable {@code true} to activate LNA module; {@code false} to deactivate LNA. * * @return result status of the operation. + * * @hide */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @Result public int setLna(boolean enable) { + TunerUtils.checkTunerPermission(mContext); return nativeSetLna(enable); } /** * Gets the statuses of the frontend. * - * This retrieve the statuses of the frontend for given status types. - * - * @param statusTypes an array of status type which the caller request. + * <p>This retrieve the statuses of the frontend for given status types. * - * @return statuses an array of statuses which response the caller's - * request. + * @param statusTypes an array of status types which the caller requests. + * @return statuses which response the caller's requests. * @hide */ - public FrontendStatus[] getFrontendStatus(int[] statusTypes) { + @Nullable + public FrontendStatus getFrontendStatus(int[] statusTypes) { return nativeGetFrontendStatus(statusTypes); } @@ -347,59 +370,77 @@ public final class Tuner implements AutoCloseable { * * @param filter the filter instance for the hardware sync ID. * @return the id of hardware A/V sync. + * * @hide */ - public int getAvSyncHwId(Filter filter) { + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + public int getAvSyncHwId(@NonNull Filter filter) { + TunerUtils.checkTunerPermission(mContext); return nativeGetAvSyncHwId(filter); } + /** - * Gets the current timestamp for A/V sync + * Gets the current timestamp for Audio/Video sync * - * The timestamp is maintained by hardware. The timestamp based on 90KHz, and it's format is the - * same as PTS (Presentation Time Stamp). + * <p>The timestamp is maintained by hardware. The timestamp based on 90KHz, and it's format is + * the same as PTS (Presentation Time Stamp). * * @param avSyncHwId the hardware id of A/V sync. * @return the current timestamp of hardware A/V sync. + * * @hide */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public long getAvSyncTime(int avSyncHwId) { + TunerUtils.checkTunerPermission(mContext); return nativeGetAvSyncTime(avSyncHwId); } - /** * Connects Conditional Access Modules (CAM) through Common Interface (CI) * - * The demux uses the output from the frontend as the input by default, and must change to use - * the output from CI-CAM as the input after this call. + * <p>The demux uses the output from the frontend as the input by default, and must change to + * use the output from CI-CAM as the input after this call. * * @param ciCamId specify CI-CAM Id to connect. * @return result status of the operation. + * * @hide */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) @Result public int connectCiCam(int ciCamId) { + TunerUtils.checkTunerPermission(mContext); return nativeConnectCiCam(ciCamId); } /** * Disconnects Conditional Access Modules (CAM) * - * The demux will use the output from the frontend as the input after this call. + * <p>The demux will use the output from the frontend as the input after this call. * * @return result status of the operation. + * * @hide */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) @Result public int disconnectCiCam() { + TunerUtils.checkTunerPermission(mContext); return nativeDisconnectCiCam(); } /** - * Retrieve the frontend information. + * Gets the frontend information. + * + * @return The frontend information. {@code null} if the operation failed. + * * @hide */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @Nullable public FrontendInfo getFrontendInfo() { + TunerUtils.checkTunerPermission(mContext); if (mFrontend == null) { throw new IllegalStateException("frontend is not initialized"); } @@ -407,10 +448,13 @@ public final class Tuner implements AutoCloseable { } /** - * Gets frontend ID. + * Gets the frontend ID. + * * @hide */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int getFrontendId() { + TunerUtils.checkTunerPermission(mContext); if (mFrontend == null) { throw new IllegalStateException("frontend is not initialized"); } @@ -419,6 +463,7 @@ public final class Tuner implements AutoCloseable { /** * Gets Demux capabilities. + * * @hide */ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) @@ -461,8 +506,26 @@ public final class Tuner implements AutoCloseable { private Filter() {} } - private Filter openFilter(@FilterType int mainType, @FilterSubtype int subType, int bufferSize, - FilterCallback cb) { + /** + * Opens a filter object based on the given types and buffer size. + * + * @param mainType the main type of the filter. + * @param subType the subtype of the filter. + * @param bufferSize the buffer size of the filter to be opened in bytes. The buffer holds the + * data output from the filter. + * @param cb the callback to receive notifications from filter. + * @param executor the executor on which callback will be invoked. The default event handler + * executor is used if it's {@code null}. + * @return the opened filter. {@code null} if the operation failed. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @Nullable + public Filter openFilter(@FilterType int mainType, @FilterSubtype int subType, + @BytesLong long bufferSize, @Nullable FilterCallback cb, + @CallbackExecutor @Nullable Executor executor) { + TunerUtils.checkTunerPermission(mContext); Filter filter = nativeOpenFilter( mainType, TunerUtils.getFilterSubtype(mainType, subType), bufferSize); if (filter != null) { @@ -474,6 +537,24 @@ public final class Tuner implements AutoCloseable { return filter; } + /** + * Opens an LNB (low-noise block downconverter) object. + * + * @param cb the callback to receive notifications from LNB. + * @param executor the executor on which callback will be invoked. The default event handler + * executor is used if it's {@code null}. + * @return the opened LNB object. {@code null} if the operation failed. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @Nullable + public Lnb openLnb(LnbCallback cb, @CallbackExecutor @Nullable Executor executor) { + TunerUtils.checkTunerPermission(mContext); + // TODO: use resource manager to get LNB ID. + return new Lnb(0); + } + private List<Integer> getLnbIds() { mLnbIds = nativeGetLnbIds(); return mLnbIds; @@ -521,7 +602,24 @@ public final class Tuner implements AutoCloseable { return nativeOpenDescrambler(); } - private Dvr openDvr(int type, int bufferSize) { + /** + * Open a DVR (Digital Video Record) instance. + * + * @param type the DVR type to be opened. + * @param bufferSize the buffer size of the output in bytes. It's used to hold output data of + * the attached filters. + * @param cb the callback to receive notifications from DVR. + * @param executor the executor on which callback will be invoked. The default event handler + * executor is used if it's {@code null}. + * @return the opened DVR object. {@code null} if the operation failed. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @Nullable + public Dvr openDvr(@DvrSettings.Type int type, @BytesLong long bufferSize, DvrCallback cb, + @CallbackExecutor @Nullable Executor executor) { + TunerUtils.checkTunerPermission(mContext); Dvr dvr = nativeOpenDvr(type, bufferSize); return dvr; } diff --git a/media/java/android/media/tv/tuner/TunerConstants.java b/media/java/android/media/tv/tuner/TunerConstants.java index c2d6c58c6558..fa8f550867b8 100644 --- a/media/java/android/media/tv/tuner/TunerConstants.java +++ b/media/java/android/media/tv/tuner/TunerConstants.java @@ -20,6 +20,11 @@ import android.annotation.IntDef; import android.annotation.LongDef; import android.annotation.SystemApi; import android.hardware.tv.tuner.V1_0.Constants; +import android.media.tv.tuner.frontend.DvbcFrontendSettings; +import android.media.tv.tuner.frontend.DvbsFrontendSettings; +import android.media.tv.tuner.frontend.Isdbs3FrontendSettings; +import android.media.tv.tuner.frontend.IsdbsFrontendSettings; +import android.media.tv.tuner.frontend.IsdbtFrontendSettings; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -124,93 +129,29 @@ public final class TunerConstants { */ public static final int FILTER_STATUS_OVERFLOW = Constants.DemuxFilterStatus.OVERFLOW; - /** - * Indexes can be tagged through TS (Transport Stream) header. - * - * @hide - */ - @IntDef(flag = true, value = {TS_INDEX_FIRST_PACKET, TS_INDEX_PAYLOAD_UNIT_START_INDICATOR, - TS_INDEX_CHANGE_TO_NOT_SCRAMBLED, TS_INDEX_CHANGE_TO_EVEN_SCRAMBLED, - TS_INDEX_CHANGE_TO_ODD_SCRAMBLED, TS_INDEX_DISCONTINUITY_INDICATOR, - TS_INDEX_RANDOM_ACCESS_INDICATOR, TS_INDEX_PRIORITY_INDICATOR, TS_INDEX_PCR_FLAG, - TS_INDEX_OPCR_FLAG, TS_INDEX_SPLICING_POINT_FLAG, TS_INDEX_PRIVATE_DATA, - TS_INDEX_ADAPTATION_EXTENSION_FLAG}) + + /** @hide */ @Retention(RetentionPolicy.SOURCE) - public @interface TsIndex {} + @IntDef(prefix = "INDEX_TYPE_", value = + {INDEX_TYPE_NONE, INDEX_TYPE_SC, INDEX_TYPE_SC_HEVC}) + public @interface ScIndexType {} /** - * TS index FIRST_PACKET. - * @hide - */ - public static final int TS_INDEX_FIRST_PACKET = Constants.DemuxTsIndex.FIRST_PACKET; - /** - * TS index PAYLOAD_UNIT_START_INDICATOR. - * @hide - */ - public static final int TS_INDEX_PAYLOAD_UNIT_START_INDICATOR = - Constants.DemuxTsIndex.PAYLOAD_UNIT_START_INDICATOR; - /** - * TS index CHANGE_TO_NOT_SCRAMBLED. - * @hide - */ - public static final int TS_INDEX_CHANGE_TO_NOT_SCRAMBLED = - Constants.DemuxTsIndex.CHANGE_TO_NOT_SCRAMBLED; - /** - * TS index CHANGE_TO_EVEN_SCRAMBLED. + * Start Code Index is not used. * @hide */ - public static final int TS_INDEX_CHANGE_TO_EVEN_SCRAMBLED = - Constants.DemuxTsIndex.CHANGE_TO_EVEN_SCRAMBLED; + public static final int INDEX_TYPE_NONE = Constants.DemuxRecordScIndexType.NONE; /** - * TS index CHANGE_TO_ODD_SCRAMBLED. + * Start Code index. * @hide */ - public static final int TS_INDEX_CHANGE_TO_ODD_SCRAMBLED = - Constants.DemuxTsIndex.CHANGE_TO_ODD_SCRAMBLED; + public static final int INDEX_TYPE_SC = Constants.DemuxRecordScIndexType.SC; /** - * TS index DISCONTINUITY_INDICATOR. + * Start Code index for HEVC. * @hide */ - public static final int TS_INDEX_DISCONTINUITY_INDICATOR = - Constants.DemuxTsIndex.DISCONTINUITY_INDICATOR; - /** - * TS index RANDOM_ACCESS_INDICATOR. - * @hide - */ - public static final int TS_INDEX_RANDOM_ACCESS_INDICATOR = - Constants.DemuxTsIndex.RANDOM_ACCESS_INDICATOR; - /** - * TS index PRIORITY_INDICATOR. - * @hide - */ - public static final int TS_INDEX_PRIORITY_INDICATOR = Constants.DemuxTsIndex.PRIORITY_INDICATOR; - /** - * TS index PCR_FLAG. - * @hide - */ - public static final int TS_INDEX_PCR_FLAG = Constants.DemuxTsIndex.PCR_FLAG; - /** - * TS index OPCR_FLAG. - * @hide - */ - public static final int TS_INDEX_OPCR_FLAG = Constants.DemuxTsIndex.OPCR_FLAG; - /** - * TS index SPLICING_POINT_FLAG. - * @hide - */ - public static final int TS_INDEX_SPLICING_POINT_FLAG = - Constants.DemuxTsIndex.SPLICING_POINT_FLAG; - /** - * TS index PRIVATE_DATA. - * @hide - */ - public static final int TS_INDEX_PRIVATE_DATA = Constants.DemuxTsIndex.PRIVATE_DATA; - /** - * TS index ADAPTATION_EXTENSION_FLAG. - * @hide - */ - public static final int TS_INDEX_ADAPTATION_EXTENSION_FLAG = - Constants.DemuxTsIndex.ADAPTATION_EXTENSION_FLAG; + public static final int INDEX_TYPE_SC_HEVC = Constants.DemuxRecordScIndexType.SC_HEVC; + /** * Indexes can be tagged by Start Code in PES (Packetized Elementary Stream) @@ -317,156 +258,6 @@ public final class TunerConstants { public static final int FRONTEND_SCAN_BLIND = Constants.FrontendScanType.SCAN_BLIND; - /** @hide */ - @IntDef({FRONTEND_STATUS_TYPE_DEMOD_LOCK, FRONTEND_STATUS_TYPE_SNR, FRONTEND_STATUS_TYPE_BER, - FRONTEND_STATUS_TYPE_PER, FRONTEND_STATUS_TYPE_PRE_BER, - FRONTEND_STATUS_TYPE_SIGNAL_QUALITY, FRONTEND_STATUS_TYPE_SIGNAL_STRENGTH, - FRONTEND_STATUS_TYPE_SYMBOL_RATE, FRONTEND_STATUS_TYPE_FEC, - FRONTEND_STATUS_TYPE_MODULATION, FRONTEND_STATUS_TYPE_SPECTRAL, - FRONTEND_STATUS_TYPE_LNB_VOLTAGE, FRONTEND_STATUS_TYPE_PLP_ID, - FRONTEND_STATUS_TYPE_EWBS, FRONTEND_STATUS_TYPE_AGC, FRONTEND_STATUS_TYPE_LNA, - FRONTEND_STATUS_TYPE_LAYER_ERROR, FRONTEND_STATUS_TYPE_VBER_CN, - FRONTEND_STATUS_TYPE_LBER_CN, FRONTEND_STATUS_TYPE_XER_CN, FRONTEND_STATUS_TYPE_MER, - FRONTEND_STATUS_TYPE_FREQ_OFFSET, FRONTEND_STATUS_TYPE_HIERARCHY, - FRONTEND_STATUS_TYPE_RF_LOCK, FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO}) - @Retention(RetentionPolicy.SOURCE) - public @interface FrontendStatusType {} - - /** - * Lock status for Demod. - * @hide - */ - public static final int FRONTEND_STATUS_TYPE_DEMOD_LOCK = - Constants.FrontendStatusType.DEMOD_LOCK; - /** - * Signal to Noise Ratio. - * @hide - */ - public static final int FRONTEND_STATUS_TYPE_SNR = Constants.FrontendStatusType.SNR; - /** - * Bit Error Ratio. - * @hide - */ - public static final int FRONTEND_STATUS_TYPE_BER = Constants.FrontendStatusType.BER; - /** - * Packages Error Ratio. - * @hide - */ - public static final int FRONTEND_STATUS_TYPE_PER = Constants.FrontendStatusType.PER; - /** - * Bit Error Ratio before FEC. - * @hide - */ - public static final int FRONTEND_STATUS_TYPE_PRE_BER = Constants.FrontendStatusType.PRE_BER; - /** - * Signal Quality (0..100). Good data over total data in percent can be - * used as a way to present Signal Quality. - * @hide - */ - public static final int FRONTEND_STATUS_TYPE_SIGNAL_QUALITY = - Constants.FrontendStatusType.SIGNAL_QUALITY; - /** - * Signal Strength. - * @hide - */ - public static final int FRONTEND_STATUS_TYPE_SIGNAL_STRENGTH = - Constants.FrontendStatusType.SIGNAL_STRENGTH; - /** - * Symbol Rate. - * @hide - */ - public static final int FRONTEND_STATUS_TYPE_SYMBOL_RATE = - Constants.FrontendStatusType.SYMBOL_RATE; - /** - * Forward Error Correction Type. - * @hide - */ - public static final int FRONTEND_STATUS_TYPE_FEC = Constants.FrontendStatusType.FEC; - /** - * Modulation Type. - * @hide - */ - public static final int FRONTEND_STATUS_TYPE_MODULATION = - Constants.FrontendStatusType.MODULATION; - /** - * Spectral Inversion Type. - * @hide - */ - public static final int FRONTEND_STATUS_TYPE_SPECTRAL = Constants.FrontendStatusType.SPECTRAL; - /** - * LNB Voltage. - * @hide - */ - public static final int FRONTEND_STATUS_TYPE_LNB_VOLTAGE = - Constants.FrontendStatusType.LNB_VOLTAGE; - /** - * Physical Layer Pipe ID. - * @hide - */ - public static final int FRONTEND_STATUS_TYPE_PLP_ID = Constants.FrontendStatusType.PLP_ID; - /** - * Status for Emergency Warning Broadcasting System. - * @hide - */ - public static final int FRONTEND_STATUS_TYPE_EWBS = Constants.FrontendStatusType.EWBS; - /** - * Automatic Gain Control. - * @hide - */ - public static final int FRONTEND_STATUS_TYPE_AGC = Constants.FrontendStatusType.AGC; - /** - * Low Noise Amplifier. - * @hide - */ - public static final int FRONTEND_STATUS_TYPE_LNA = Constants.FrontendStatusType.LNA; - /** - * Error status by layer. - * @hide - */ - public static final int FRONTEND_STATUS_TYPE_LAYER_ERROR = - Constants.FrontendStatusType.LAYER_ERROR; - /** - * CN value by VBER. - * @hide - */ - public static final int FRONTEND_STATUS_TYPE_VBER_CN = Constants.FrontendStatusType.VBER_CN; - /** - * CN value by LBER. - * @hide - */ - public static final int FRONTEND_STATUS_TYPE_LBER_CN = Constants.FrontendStatusType.LBER_CN; - /** - * CN value by XER. - * @hide - */ - public static final int FRONTEND_STATUS_TYPE_XER_CN = Constants.FrontendStatusType.XER_CN; - /** - * Moduration Error Ratio. - * @hide - */ - public static final int FRONTEND_STATUS_TYPE_MER = Constants.FrontendStatusType.MER; - /** - * Difference between tuning frequency and actual locked frequency. - * @hide - */ - public static final int FRONTEND_STATUS_TYPE_FREQ_OFFSET = - Constants.FrontendStatusType.FREQ_OFFSET; - /** - * Hierarchy for DVBT. - * @hide - */ - public static final int FRONTEND_STATUS_TYPE_HIERARCHY = Constants.FrontendStatusType.HIERARCHY; - /** - * Lock status for RF. - * @hide - */ - public static final int FRONTEND_STATUS_TYPE_RF_LOCK = Constants.FrontendStatusType.RF_LOCK; - /** - * PLP information in a frequency band for ATSC3.0 frontend. - * @hide - */ - public static final int FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO = - Constants.FrontendStatusType.ATSC3_PLP_INFO; /** @hide */ @LongDef({FEC_UNDEFINED, FEC_AUTO, FEC_1_2, FEC_1_3, FEC_1_4, FEC_1_5, FEC_2_3, FEC_2_5, @@ -665,615 +456,52 @@ public final class TunerConstants { /** @hide */ - @IntDef({DVBC_MODULATION_UNDEFINED, DVBC_MODULATION_AUTO, DVBC_MODULATION_MOD_16QAM, - DVBC_MODULATION_MOD_32QAM, DVBC_MODULATION_MOD_64QAM, DVBC_MODULATION_MOD_128QAM, - DVBC_MODULATION_MOD_256QAM, DVBS_MODULATION_UNDEFINED, DVBS_MODULATION_AUTO, - DVBS_MODULATION_MOD_QPSK, DVBS_MODULATION_MOD_8PSK, DVBS_MODULATION_MOD_16QAM, - DVBS_MODULATION_MOD_16PSK, DVBS_MODULATION_MOD_32PSK, DVBS_MODULATION_MOD_ACM, - DVBS_MODULATION_MOD_8APSK, DVBS_MODULATION_MOD_16APSK, DVBS_MODULATION_MOD_32APSK, - DVBS_MODULATION_MOD_64APSK, DVBS_MODULATION_MOD_128APSK, DVBS_MODULATION_MOD_256APSK, - DVBS_MODULATION_MOD_RESERVED, ISDBS_MODULATION_UNDEFINED, ISDBS_MODULATION_AUTO, - ISDBS_MODULATION_MOD_BPSK, ISDBS_MODULATION_MOD_QPSK, ISDBS_MODULATION_MOD_TC8PSK, - ISDBS3_MODULATION_UNDEFINED, ISDBS3_MODULATION_AUTO, ISDBS3_MODULATION_MOD_BPSK, - ISDBS3_MODULATION_MOD_QPSK, ISDBS3_MODULATION_MOD_8PSK, ISDBS3_MODULATION_MOD_16APSK, - ISDBS3_MODULATION_MOD_32APSK, ISDBT_MODULATION_UNDEFINED, ISDBT_MODULATION_AUTO, - ISDBT_MODULATION_MOD_DQPSK, ISDBT_MODULATION_MOD_QPSK, ISDBT_MODULATION_MOD_16QAM, - ISDBT_MODULATION_MOD_64QAM}) + @IntDef(value = { + DvbcFrontendSettings.MODULATION_UNDEFINED, + DvbcFrontendSettings.MODULATION_AUTO, + DvbcFrontendSettings.MODULATION_MOD_16QAM, + DvbcFrontendSettings.MODULATION_MOD_32QAM, + DvbcFrontendSettings.MODULATION_MOD_64QAM, + DvbcFrontendSettings.MODULATION_MOD_128QAM, + DvbcFrontendSettings.MODULATION_MOD_256QAM, + DvbsFrontendSettings.MODULATION_UNDEFINED, + DvbsFrontendSettings.MODULATION_AUTO, + DvbsFrontendSettings.MODULATION_MOD_QPSK, + DvbsFrontendSettings.MODULATION_MOD_8PSK, + DvbsFrontendSettings.MODULATION_MOD_16QAM, + DvbsFrontendSettings.MODULATION_MOD_16PSK, + DvbsFrontendSettings.MODULATION_MOD_32PSK, + DvbsFrontendSettings.MODULATION_MOD_ACM, + DvbsFrontendSettings.MODULATION_MOD_8APSK, + DvbsFrontendSettings.MODULATION_MOD_16APSK, + DvbsFrontendSettings.MODULATION_MOD_32APSK, + DvbsFrontendSettings.MODULATION_MOD_64APSK, + DvbsFrontendSettings.MODULATION_MOD_128APSK, + DvbsFrontendSettings.MODULATION_MOD_256APSK, + DvbsFrontendSettings.MODULATION_MOD_RESERVED, + IsdbsFrontendSettings.MODULATION_UNDEFINED, + IsdbsFrontendSettings.MODULATION_AUTO, + IsdbsFrontendSettings.MODULATION_MOD_BPSK, + IsdbsFrontendSettings.MODULATION_MOD_QPSK, + IsdbsFrontendSettings.MODULATION_MOD_TC8PSK, + Isdbs3FrontendSettings.MODULATION_UNDEFINED, + Isdbs3FrontendSettings.MODULATION_AUTO, + Isdbs3FrontendSettings.MODULATION_MOD_BPSK, + Isdbs3FrontendSettings.MODULATION_MOD_QPSK, + Isdbs3FrontendSettings.MODULATION_MOD_8PSK, + Isdbs3FrontendSettings.MODULATION_MOD_16APSK, + Isdbs3FrontendSettings.MODULATION_MOD_32APSK, + IsdbtFrontendSettings.MODULATION_UNDEFINED, + IsdbtFrontendSettings.MODULATION_AUTO, + IsdbtFrontendSettings.MODULATION_MOD_DQPSK, + IsdbtFrontendSettings.MODULATION_MOD_QPSK, + IsdbtFrontendSettings.MODULATION_MOD_16QAM, + IsdbtFrontendSettings.MODULATION_MOD_64QAM}) @Retention(RetentionPolicy.SOURCE) public @interface FrontendModulation {} - /** @hide */ - public static final int DVBC_MODULATION_UNDEFINED = Constants.FrontendDvbcModulation.UNDEFINED; - /** @hide */ - public static final int DVBC_MODULATION_AUTO = Constants.FrontendDvbcModulation.AUTO; - /** @hide */ - public static final int DVBC_MODULATION_MOD_16QAM = Constants.FrontendDvbcModulation.MOD_16QAM; - /** @hide */ - public static final int DVBC_MODULATION_MOD_32QAM = Constants.FrontendDvbcModulation.MOD_32QAM; - /** @hide */ - public static final int DVBC_MODULATION_MOD_64QAM = Constants.FrontendDvbcModulation.MOD_64QAM; - /** @hide */ - public static final int DVBC_MODULATION_MOD_128QAM = - Constants.FrontendDvbcModulation.MOD_128QAM; - /** @hide */ - public static final int DVBC_MODULATION_MOD_256QAM = - Constants.FrontendDvbcModulation.MOD_256QAM; - /** @hide */ - public static final int DVBS_MODULATION_UNDEFINED = Constants.FrontendDvbsModulation.UNDEFINED; - /** @hide */ - public static final int DVBS_MODULATION_AUTO = Constants.FrontendDvbsModulation.AUTO; - /** @hide */ - public static final int DVBS_MODULATION_MOD_QPSK = Constants.FrontendDvbsModulation.MOD_QPSK; - /** @hide */ - public static final int DVBS_MODULATION_MOD_8PSK = Constants.FrontendDvbsModulation.MOD_8PSK; - /** @hide */ - public static final int DVBS_MODULATION_MOD_16QAM = Constants.FrontendDvbsModulation.MOD_16QAM; - /** @hide */ - public static final int DVBS_MODULATION_MOD_16PSK = Constants.FrontendDvbsModulation.MOD_16PSK; - /** @hide */ - public static final int DVBS_MODULATION_MOD_32PSK = Constants.FrontendDvbsModulation.MOD_32PSK; - /** @hide */ - public static final int DVBS_MODULATION_MOD_ACM = Constants.FrontendDvbsModulation.MOD_ACM; - /** @hide */ - public static final int DVBS_MODULATION_MOD_8APSK = Constants.FrontendDvbsModulation.MOD_8APSK; - /** @hide */ - public static final int DVBS_MODULATION_MOD_16APSK = - Constants.FrontendDvbsModulation.MOD_16APSK; - /** @hide */ - public static final int DVBS_MODULATION_MOD_32APSK = - Constants.FrontendDvbsModulation.MOD_32APSK; - /** @hide */ - public static final int DVBS_MODULATION_MOD_64APSK = - Constants.FrontendDvbsModulation.MOD_64APSK; - /** @hide */ - public static final int DVBS_MODULATION_MOD_128APSK = - Constants.FrontendDvbsModulation.MOD_128APSK; - /** @hide */ - public static final int DVBS_MODULATION_MOD_256APSK = - Constants.FrontendDvbsModulation.MOD_256APSK; - /** @hide */ - public static final int DVBS_MODULATION_MOD_RESERVED = - Constants.FrontendDvbsModulation.MOD_RESERVED; - /** @hide */ - public static final int ISDBS_MODULATION_UNDEFINED = - Constants.FrontendIsdbsModulation.UNDEFINED; - /** @hide */ - public static final int ISDBS_MODULATION_AUTO = Constants.FrontendIsdbsModulation.AUTO; - /** @hide */ - public static final int ISDBS_MODULATION_MOD_BPSK = Constants.FrontendIsdbsModulation.MOD_BPSK; - /** @hide */ - public static final int ISDBS_MODULATION_MOD_QPSK = Constants.FrontendIsdbsModulation.MOD_QPSK; - /** @hide */ - public static final int ISDBS_MODULATION_MOD_TC8PSK = - Constants.FrontendIsdbsModulation.MOD_TC8PSK; - /** @hide */ - public static final int ISDBS3_MODULATION_UNDEFINED = - Constants.FrontendIsdbs3Modulation.UNDEFINED; - /** @hide */ - public static final int ISDBS3_MODULATION_AUTO = Constants.FrontendIsdbs3Modulation.AUTO; - /** @hide */ - public static final int ISDBS3_MODULATION_MOD_BPSK = - Constants.FrontendIsdbs3Modulation.MOD_BPSK; - /** @hide */ - public static final int ISDBS3_MODULATION_MOD_QPSK = - Constants.FrontendIsdbs3Modulation.MOD_QPSK; - /** @hide */ - public static final int ISDBS3_MODULATION_MOD_8PSK = - Constants.FrontendIsdbs3Modulation.MOD_8PSK; - /** @hide */ - public static final int ISDBS3_MODULATION_MOD_16APSK = - Constants.FrontendIsdbs3Modulation.MOD_16APSK; - /** @hide */ - public static final int ISDBS3_MODULATION_MOD_32APSK = - Constants.FrontendIsdbs3Modulation.MOD_32APSK; - /** @hide */ - public static final int ISDBT_MODULATION_UNDEFINED = - Constants.FrontendIsdbtModulation.UNDEFINED; - /** @hide */ - public static final int ISDBT_MODULATION_AUTO = Constants.FrontendIsdbtModulation.AUTO; - /** @hide */ - public static final int ISDBT_MODULATION_MOD_DQPSK = - Constants.FrontendIsdbtModulation.MOD_DQPSK; - /** @hide */ - public static final int ISDBT_MODULATION_MOD_QPSK = Constants.FrontendIsdbtModulation.MOD_QPSK; - /** @hide */ - public static final int ISDBT_MODULATION_MOD_16QAM = - Constants.FrontendIsdbtModulation.MOD_16QAM; - /** @hide */ - public static final int ISDBT_MODULATION_MOD_64QAM = - Constants.FrontendIsdbtModulation.MOD_64QAM; /** @hide */ - @IntDef({SPECTRAL_INVERSION_UNDEFINED, SPECTRAL_INVERSION_NORMAL, SPECTRAL_INVERSION_INVERTED}) - @Retention(RetentionPolicy.SOURCE) - public @interface FrontendDvbcSpectralInversion {} - /** @hide */ - public static final int SPECTRAL_INVERSION_UNDEFINED = - Constants.FrontendDvbcSpectralInversion.UNDEFINED; - /** @hide */ - public static final int SPECTRAL_INVERSION_NORMAL = - Constants.FrontendDvbcSpectralInversion.NORMAL; - /** @hide */ - public static final int SPECTRAL_INVERSION_INVERTED = - Constants.FrontendDvbcSpectralInversion.INVERTED; - - - /** @hide */ - @IntDef({HIERARCHY_UNDEFINED, HIERARCHY_AUTO, HIERARCHY_NON_NATIVE, HIERARCHY_1_NATIVE, - HIERARCHY_2_NATIVE, HIERARCHY_4_NATIVE, HIERARCHY_NON_INDEPTH, HIERARCHY_1_INDEPTH, - HIERARCHY_2_INDEPTH, HIERARCHY_4_INDEPTH}) - @Retention(RetentionPolicy.SOURCE) - public @interface FrontendDvbtHierarchy {} - /** @hide */ - public static final int HIERARCHY_UNDEFINED = Constants.FrontendDvbtHierarchy.UNDEFINED; - /** @hide */ - public static final int HIERARCHY_AUTO = Constants.FrontendDvbtHierarchy.AUTO; - /** @hide */ - public static final int HIERARCHY_NON_NATIVE = - Constants.FrontendDvbtHierarchy.HIERARCHY_NON_NATIVE; - /** @hide */ - public static final int HIERARCHY_1_NATIVE = Constants.FrontendDvbtHierarchy.HIERARCHY_1_NATIVE; - /** @hide */ - public static final int HIERARCHY_2_NATIVE = Constants.FrontendDvbtHierarchy.HIERARCHY_2_NATIVE; - /** @hide */ - public static final int HIERARCHY_4_NATIVE = Constants.FrontendDvbtHierarchy.HIERARCHY_4_NATIVE; - /** @hide */ - public static final int HIERARCHY_NON_INDEPTH = - Constants.FrontendDvbtHierarchy.HIERARCHY_NON_INDEPTH; - /** @hide */ - public static final int HIERARCHY_1_INDEPTH = - Constants.FrontendDvbtHierarchy.HIERARCHY_1_INDEPTH; - /** @hide */ - public static final int HIERARCHY_2_INDEPTH = - Constants.FrontendDvbtHierarchy.HIERARCHY_2_INDEPTH; - /** @hide */ - public static final int HIERARCHY_4_INDEPTH = - Constants.FrontendDvbtHierarchy.HIERARCHY_4_INDEPTH; - - /** @hide */ - @IntDef({FRONTEND_ATSC_MODULATION_UNDEFINED, FRONTEND_ATSC_MODULATION_AUTO, - FRONTEND_ATSC_MODULATION_MOD_8VSB, FRONTEND_ATSC_MODULATION_MOD_16VSB}) - @Retention(RetentionPolicy.SOURCE) - public @interface FrontendAtscModulation {} - /** @hide */ - public static final int FRONTEND_ATSC_MODULATION_UNDEFINED = - Constants.FrontendAtscModulation.UNDEFINED; - /** @hide */ - public static final int FRONTEND_ATSC_MODULATION_AUTO = Constants.FrontendAtscModulation.AUTO; - /** @hide */ - public static final int FRONTEND_ATSC_MODULATION_MOD_8VSB = - Constants.FrontendAtscModulation.MOD_8VSB; - /** @hide */ - public static final int FRONTEND_ATSC_MODULATION_MOD_16VSB = - Constants.FrontendAtscModulation.MOD_16VSB; - - /** @hide */ - @IntDef({FRONTEND_ATSC3_BANDWIDTH_UNDEFINED, FRONTEND_ATSC3_BANDWIDTH_AUTO, - FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_6MHZ, FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_7MHZ, - FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_8MHZ}) - @Retention(RetentionPolicy.SOURCE) - public @interface FrontendAtsc3Bandwidth {} - /** @hide */ - public static final int FRONTEND_ATSC3_BANDWIDTH_UNDEFINED = - Constants.FrontendAtsc3Bandwidth.UNDEFINED; - /** @hide */ - public static final int FRONTEND_ATSC3_BANDWIDTH_AUTO = Constants.FrontendAtsc3Bandwidth.AUTO; - /** @hide */ - public static final int FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_6MHZ = - Constants.FrontendAtsc3Bandwidth.BANDWIDTH_6MHZ; - /** @hide */ - public static final int FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_7MHZ = - Constants.FrontendAtsc3Bandwidth.BANDWIDTH_7MHZ; - /** @hide */ - public static final int FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_8MHZ = - Constants.FrontendAtsc3Bandwidth.BANDWIDTH_8MHZ; - - /** @hide */ - @IntDef({FRONTEND_ATSC3_MODULATION_UNDEFINED, FRONTEND_ATSC3_MODULATION_AUTO, - FRONTEND_ATSC3_MODULATION_MOD_QPSK, FRONTEND_ATSC3_MODULATION_MOD_16QAM, - FRONTEND_ATSC3_MODULATION_MOD_64QAM, FRONTEND_ATSC3_MODULATION_MOD_256QAM, - FRONTEND_ATSC3_MODULATION_MOD_1024QAM, FRONTEND_ATSC3_MODULATION_MOD_4096QAM}) - @Retention(RetentionPolicy.SOURCE) - public @interface FrontendAtsc3Modulation {} - /** @hide */ - public static final int FRONTEND_ATSC3_MODULATION_UNDEFINED = - Constants.FrontendAtsc3Modulation.UNDEFINED; - /** @hide */ - public static final int FRONTEND_ATSC3_MODULATION_AUTO = Constants.FrontendAtsc3Modulation.AUTO; - /** @hide */ - public static final int FRONTEND_ATSC3_MODULATION_MOD_QPSK = - Constants.FrontendAtsc3Modulation.MOD_QPSK; - /** @hide */ - public static final int FRONTEND_ATSC3_MODULATION_MOD_16QAM = - Constants.FrontendAtsc3Modulation.MOD_16QAM; - /** @hide */ - public static final int FRONTEND_ATSC3_MODULATION_MOD_64QAM = - Constants.FrontendAtsc3Modulation.MOD_64QAM; - /** @hide */ - public static final int FRONTEND_ATSC3_MODULATION_MOD_256QAM = - Constants.FrontendAtsc3Modulation.MOD_256QAM; - /** @hide */ - public static final int FRONTEND_ATSC3_MODULATION_MOD_1024QAM = - Constants.FrontendAtsc3Modulation.MOD_1024QAM; - /** @hide */ - public static final int FRONTEND_ATSC3_MODULATION_MOD_4096QAM = - Constants.FrontendAtsc3Modulation.MOD_4096QAM; - - /** @hide */ - @IntDef({FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_UNDEFINED, - FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_AUTO, FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_CTI, - FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_HTI}) - @Retention(RetentionPolicy.SOURCE) - public @interface FrontendAtsc3TimeInterleaveMode {} - /** @hide */ - public static final int FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_UNDEFINED = - Constants.FrontendAtsc3TimeInterleaveMode.UNDEFINED; - /** @hide */ - public static final int FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_AUTO = - Constants.FrontendAtsc3TimeInterleaveMode.AUTO; - /** @hide */ - public static final int FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_CTI = - Constants.FrontendAtsc3TimeInterleaveMode.CTI; - /** @hide */ - public static final int FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_HTI = - Constants.FrontendAtsc3TimeInterleaveMode.HTI; - - /** @hide */ - @IntDef({FRONTEND_ATSC3_CODERATE_UNDEFINED, FRONTEND_ATSC3_CODERATE_AUTO, - FRONTEND_ATSC3_CODERATE_2_15, FRONTEND_ATSC3_CODERATE_3_15, - FRONTEND_ATSC3_CODERATE_4_15, FRONTEND_ATSC3_CODERATE_5_15, - FRONTEND_ATSC3_CODERATE_6_15, FRONTEND_ATSC3_CODERATE_7_15, - FRONTEND_ATSC3_CODERATE_8_15, FRONTEND_ATSC3_CODERATE_9_15, - FRONTEND_ATSC3_CODERATE_10_15, FRONTEND_ATSC3_CODERATE_11_15, - FRONTEND_ATSC3_CODERATE_12_15, FRONTEND_ATSC3_CODERATE_13_15}) - @Retention(RetentionPolicy.SOURCE) - public @interface FrontendAtsc3CodeRate {} - /** @hide */ - public static final int FRONTEND_ATSC3_CODERATE_UNDEFINED = - Constants.FrontendAtsc3CodeRate.UNDEFINED; - /** @hide */ - public static final int FRONTEND_ATSC3_CODERATE_AUTO = Constants.FrontendAtsc3CodeRate.AUTO; - /** @hide */ - public static final int FRONTEND_ATSC3_CODERATE_2_15 = - Constants.FrontendAtsc3CodeRate.CODERATE_2_15; - /** @hide */ - public static final int FRONTEND_ATSC3_CODERATE_3_15 = - Constants.FrontendAtsc3CodeRate.CODERATE_3_15; - /** @hide */ - public static final int FRONTEND_ATSC3_CODERATE_4_15 = - Constants.FrontendAtsc3CodeRate.CODERATE_4_15; - /** @hide */ - public static final int FRONTEND_ATSC3_CODERATE_5_15 = - Constants.FrontendAtsc3CodeRate.CODERATE_5_15; - /** @hide */ - public static final int FRONTEND_ATSC3_CODERATE_6_15 = - Constants.FrontendAtsc3CodeRate.CODERATE_6_15; - /** @hide */ - public static final int FRONTEND_ATSC3_CODERATE_7_15 = - Constants.FrontendAtsc3CodeRate.CODERATE_7_15; - /** @hide */ - public static final int FRONTEND_ATSC3_CODERATE_8_15 = - Constants.FrontendAtsc3CodeRate.CODERATE_8_15; - /** @hide */ - public static final int FRONTEND_ATSC3_CODERATE_9_15 = - Constants.FrontendAtsc3CodeRate.CODERATE_9_15; - /** @hide */ - public static final int FRONTEND_ATSC3_CODERATE_10_15 = - Constants.FrontendAtsc3CodeRate.CODERATE_10_15; - /** @hide */ - public static final int FRONTEND_ATSC3_CODERATE_11_15 = - Constants.FrontendAtsc3CodeRate.CODERATE_11_15; - /** @hide */ - public static final int FRONTEND_ATSC3_CODERATE_12_15 = - Constants.FrontendAtsc3CodeRate.CODERATE_12_15; - /** @hide */ - public static final int FRONTEND_ATSC3_CODERATE_13_15 = - Constants.FrontendAtsc3CodeRate.CODERATE_13_15; - - /** @hide */ - @IntDef({FRONTEND_ATSC3_FEC_UNDEFINED, FRONTEND_ATSC3_FEC_AUTO, FRONTEND_ATSC3_FEC_BCH_LDPC_16K, - FRONTEND_ATSC3_FEC_BCH_LDPC_64K, FRONTEND_ATSC3_FEC_CRC_LDPC_16K, - FRONTEND_ATSC3_FEC_CRC_LDPC_64K, FRONTEND_ATSC3_FEC_LDPC_16K, - FRONTEND_ATSC3_FEC_LDPC_64K}) - @Retention(RetentionPolicy.SOURCE) - public @interface FrontendAtsc3Fec {} - /** @hide */ - public static final int FRONTEND_ATSC3_FEC_UNDEFINED = Constants.FrontendAtsc3Fec.UNDEFINED; - /** @hide */ - public static final int FRONTEND_ATSC3_FEC_AUTO = Constants.FrontendAtsc3Fec.AUTO; - /** @hide */ - public static final int FRONTEND_ATSC3_FEC_BCH_LDPC_16K = - Constants.FrontendAtsc3Fec.BCH_LDPC_16K; - /** @hide */ - public static final int FRONTEND_ATSC3_FEC_BCH_LDPC_64K = - Constants.FrontendAtsc3Fec.BCH_LDPC_64K; - /** @hide */ - public static final int FRONTEND_ATSC3_FEC_CRC_LDPC_16K = - Constants.FrontendAtsc3Fec.CRC_LDPC_16K; - /** @hide */ - public static final int FRONTEND_ATSC3_FEC_CRC_LDPC_64K = - Constants.FrontendAtsc3Fec.CRC_LDPC_64K; - /** @hide */ - public static final int FRONTEND_ATSC3_FEC_LDPC_16K = Constants.FrontendAtsc3Fec.LDPC_16K; - /** @hide */ - public static final int FRONTEND_ATSC3_FEC_LDPC_64K = Constants.FrontendAtsc3Fec.LDPC_64K; - - /** @hide */ - @IntDef({FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_UNDEFINED, - FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_ATSC3_LINKLAYER_PACKET, - FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_BASEBAND_PACKET}) - @Retention(RetentionPolicy.SOURCE) - public @interface FrontendAtsc3DemodOutputFormat {} - /** @hide */ - public static final int FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_UNDEFINED = - Constants.FrontendAtsc3DemodOutputFormat.UNDEFINED; - /** @hide */ - public static final int FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_ATSC3_LINKLAYER_PACKET = - Constants.FrontendAtsc3DemodOutputFormat.ATSC3_LINKLAYER_PACKET; - /** @hide */ - public static final int FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_BASEBAND_PACKET = - Constants.FrontendAtsc3DemodOutputFormat.BASEBAND_PACKET; - - /** @hide */ - @IntDef({FRONTEND_DVBS_STANDARD_AUTO, FRONTEND_DVBS_STANDARD_S, FRONTEND_DVBS_STANDARD_S2, - FRONTEND_DVBS_STANDARD_S2X}) - @Retention(RetentionPolicy.SOURCE) - public @interface FrontendDvbsStandard {} - /** @hide */ - public static final int FRONTEND_DVBS_STANDARD_AUTO = Constants.FrontendDvbsStandard.AUTO; - /** @hide */ - public static final int FRONTEND_DVBS_STANDARD_S = Constants.FrontendDvbsStandard.S; - /** @hide */ - public static final int FRONTEND_DVBS_STANDARD_S2 = Constants.FrontendDvbsStandard.S2; - /** @hide */ - public static final int FRONTEND_DVBS_STANDARD_S2X = Constants.FrontendDvbsStandard.S2X; - - /** @hide */ - @IntDef({FRONTEND_DVBC_ANNEX_UNDEFINED, FRONTEND_DVBC_ANNEX_A, FRONTEND_DVBC_ANNEX_B, - FRONTEND_DVBC_ANNEX_C}) - @Retention(RetentionPolicy.SOURCE) - public @interface FrontendDvbcAnnex {} - /** @hide */ - public static final int FRONTEND_DVBC_ANNEX_UNDEFINED = Constants.FrontendDvbcAnnex.UNDEFINED; - /** @hide */ - public static final int FRONTEND_DVBC_ANNEX_A = Constants.FrontendDvbcAnnex.A; - /** @hide */ - public static final int FRONTEND_DVBC_ANNEX_B = Constants.FrontendDvbcAnnex.B; - /** @hide */ - public static final int FRONTEND_DVBC_ANNEX_C = Constants.FrontendDvbcAnnex.C; - - /** @hide */ - @IntDef({FRONTEND_DVBT_TRANSMISSION_MODE_UNDEFINED, FRONTEND_DVBT_TRANSMISSION_MODE_AUTO, - FRONTEND_DVBT_TRANSMISSION_MODE_2K, FRONTEND_DVBT_TRANSMISSION_MODE_8K, - FRONTEND_DVBT_TRANSMISSION_MODE_4K, FRONTEND_DVBT_TRANSMISSION_MODE_1K, - FRONTEND_DVBT_TRANSMISSION_MODE_16K, FRONTEND_DVBT_TRANSMISSION_MODE_32K}) - @Retention(RetentionPolicy.SOURCE) - public @interface FrontendDvbtTransmissionMode {} - /** @hide */ - public static final int FRONTEND_DVBT_TRANSMISSION_MODE_UNDEFINED = - Constants.FrontendDvbtTransmissionMode.UNDEFINED; - /** @hide */ - public static final int FRONTEND_DVBT_TRANSMISSION_MODE_AUTO = - Constants.FrontendDvbtTransmissionMode.AUTO; - /** @hide */ - public static final int FRONTEND_DVBT_TRANSMISSION_MODE_2K = - Constants.FrontendDvbtTransmissionMode.MODE_2K; - /** @hide */ - public static final int FRONTEND_DVBT_TRANSMISSION_MODE_8K = - Constants.FrontendDvbtTransmissionMode.MODE_8K; - /** @hide */ - public static final int FRONTEND_DVBT_TRANSMISSION_MODE_4K = - Constants.FrontendDvbtTransmissionMode.MODE_4K; - /** @hide */ - public static final int FRONTEND_DVBT_TRANSMISSION_MODE_1K = - Constants.FrontendDvbtTransmissionMode.MODE_1K; - /** @hide */ - public static final int FRONTEND_DVBT_TRANSMISSION_MODE_16K = - Constants.FrontendDvbtTransmissionMode.MODE_16K; - /** @hide */ - public static final int FRONTEND_DVBT_TRANSMISSION_MODE_32K = - Constants.FrontendDvbtTransmissionMode.MODE_32K; - - /** @hide */ - @IntDef({FRONTEND_DVBT_BANDWIDTH_UNDEFINED, FRONTEND_DVBT_BANDWIDTH_AUTO, - FRONTEND_DVBT_BANDWIDTH_8MHZ, FRONTEND_DVBT_BANDWIDTH_7MHZ, - FRONTEND_DVBT_BANDWIDTH_6MHZ, FRONTEND_DVBT_BANDWIDTH_5MHZ, - FRONTEND_DVBT_BANDWIDTH_1_7MHZ, FRONTEND_DVBT_BANDWIDTH_10MHZ}) - @Retention(RetentionPolicy.SOURCE) - public @interface FrontendDvbtBandwidth {} - /** @hide */ - public static final int FRONTEND_DVBT_BANDWIDTH_UNDEFINED = - Constants.FrontendDvbtBandwidth.UNDEFINED; - /** @hide */ - public static final int FRONTEND_DVBT_BANDWIDTH_AUTO = Constants.FrontendDvbtBandwidth.AUTO; - /** @hide */ - public static final int FRONTEND_DVBT_BANDWIDTH_8MHZ = - Constants.FrontendDvbtBandwidth.BANDWIDTH_8MHZ; - /** @hide */ - public static final int FRONTEND_DVBT_BANDWIDTH_7MHZ = - Constants.FrontendDvbtBandwidth.BANDWIDTH_7MHZ; - /** @hide */ - public static final int FRONTEND_DVBT_BANDWIDTH_6MHZ = - Constants.FrontendDvbtBandwidth.BANDWIDTH_6MHZ; - /** @hide */ - public static final int FRONTEND_DVBT_BANDWIDTH_5MHZ = - Constants.FrontendDvbtBandwidth.BANDWIDTH_5MHZ; - /** @hide */ - public static final int FRONTEND_DVBT_BANDWIDTH_1_7MHZ = - Constants.FrontendDvbtBandwidth.BANDWIDTH_1_7MHZ; - /** @hide */ - public static final int FRONTEND_DVBT_BANDWIDTH_10MHZ = - Constants.FrontendDvbtBandwidth.BANDWIDTH_10MHZ; - - /** @hide */ - @IntDef({FRONTEND_DVBT_CONSTELLATION_UNDEFINED, FRONTEND_DVBT_CONSTELLATION_AUTO, - FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_QPSK, - FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_16QAM, - FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_64QAM, - FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_256QAM}) - @Retention(RetentionPolicy.SOURCE) - public @interface FrontendDvbtConstellation {} - /** @hide */ - public static final int FRONTEND_DVBT_CONSTELLATION_UNDEFINED = - Constants.FrontendDvbtConstellation.UNDEFINED; - /** @hide */ - public static final int FRONTEND_DVBT_CONSTELLATION_AUTO = - Constants.FrontendDvbtConstellation.AUTO; - /** @hide */ - public static final int FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_QPSK = - Constants.FrontendDvbtConstellation.CONSTELLATION_QPSK; - /** @hide */ - public static final int FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_16QAM = - Constants.FrontendDvbtConstellation.CONSTELLATION_16QAM; - /** @hide */ - public static final int FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_64QAM = - Constants.FrontendDvbtConstellation.CONSTELLATION_64QAM; - /** @hide */ - public static final int FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_256QAM = - Constants.FrontendDvbtConstellation.CONSTELLATION_256QAM; - - /** @hide */ - @IntDef({FRONTEND_DVBT_CODERATE_UNDEFINED, FRONTEND_DVBT_CODERATE_AUTO, - FRONTEND_DVBT_CODERATE_1_2, FRONTEND_DVBT_CODERATE_2_3, FRONTEND_DVBT_CODERATE_3_4, - FRONTEND_DVBT_CODERATE_5_6, FRONTEND_DVBT_CODERATE_7_8, FRONTEND_DVBT_CODERATE_3_5, - FRONTEND_DVBT_CODERATE_4_5, FRONTEND_DVBT_CODERATE_6_7, FRONTEND_DVBT_CODERATE_8_9}) - @Retention(RetentionPolicy.SOURCE) - public @interface FrontendDvbtCoderate {} - /** @hide */ - public static final int FRONTEND_DVBT_CODERATE_UNDEFINED = - Constants.FrontendDvbtCoderate.UNDEFINED; - /** @hide */ - public static final int FRONTEND_DVBT_CODERATE_AUTO = Constants.FrontendDvbtCoderate.AUTO; - /** @hide */ - public static final int FRONTEND_DVBT_CODERATE_1_2 = - Constants.FrontendDvbtCoderate.CODERATE_1_2; - /** @hide */ - public static final int FRONTEND_DVBT_CODERATE_2_3 = - Constants.FrontendDvbtCoderate.CODERATE_2_3; - /** @hide */ - public static final int FRONTEND_DVBT_CODERATE_3_4 = - Constants.FrontendDvbtCoderate.CODERATE_3_4; - /** @hide */ - public static final int FRONTEND_DVBT_CODERATE_5_6 = - Constants.FrontendDvbtCoderate.CODERATE_5_6; - /** @hide */ - public static final int FRONTEND_DVBT_CODERATE_7_8 = - Constants.FrontendDvbtCoderate.CODERATE_7_8; - /** @hide */ - public static final int FRONTEND_DVBT_CODERATE_3_5 = - Constants.FrontendDvbtCoderate.CODERATE_3_5; - /** @hide */ - public static final int FRONTEND_DVBT_CODERATE_4_5 = - Constants.FrontendDvbtCoderate.CODERATE_4_5; - /** @hide */ - public static final int FRONTEND_DVBT_CODERATE_6_7 = - Constants.FrontendDvbtCoderate.CODERATE_6_7; - /** @hide */ - public static final int FRONTEND_DVBT_CODERATE_8_9 = - Constants.FrontendDvbtCoderate.CODERATE_8_9; - - /** @hide */ - @IntDef({FRONTEND_DVBT_GUARD_INTERVAL_UNDEFINED, FRONTEND_DVBT_GUARD_INTERVAL_AUTO, - FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_32, FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_16, - FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_8, FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_4, - FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_128, - FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_19_128, - FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_19_256}) - @Retention(RetentionPolicy.SOURCE) - public @interface FrontendDvbtGuardInterval {} - /** @hide */ - public static final int FRONTEND_DVBT_GUARD_INTERVAL_UNDEFINED = - Constants.FrontendDvbtGuardInterval.UNDEFINED; - /** @hide */ - public static final int FRONTEND_DVBT_GUARD_INTERVAL_AUTO = - Constants.FrontendDvbtGuardInterval.AUTO; - /** @hide */ - public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_32 = - Constants.FrontendDvbtGuardInterval.INTERVAL_1_32; - /** @hide */ - public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_16 = - Constants.FrontendDvbtGuardInterval.INTERVAL_1_16; - /** @hide */ - public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_8 = - Constants.FrontendDvbtGuardInterval.INTERVAL_1_8; - /** @hide */ - public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_4 = - Constants.FrontendDvbtGuardInterval.INTERVAL_1_4; - /** @hide */ - public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_128 = - Constants.FrontendDvbtGuardInterval.INTERVAL_1_128; - /** @hide */ - public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_19_128 = - Constants.FrontendDvbtGuardInterval.INTERVAL_19_128; - /** @hide */ - public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_19_256 = - Constants.FrontendDvbtGuardInterval.INTERVAL_19_256; - - /** @hide */ - @IntDef({FRONTEND_ISDBS_CODERATE_UNDEFINED, FRONTEND_ISDBS_CODERATE_AUTO, - FRONTEND_ISDBS_CODERATE_1_2, FRONTEND_ISDBS_CODERATE_2_3, FRONTEND_ISDBS_CODERATE_3_4, - FRONTEND_ISDBS_CODERATE_5_6, FRONTEND_ISDBS_CODERATE_7_8}) - @Retention(RetentionPolicy.SOURCE) - public @interface FrontendIsdbsCoderate {} - /** @hide */ - public static final int FRONTEND_ISDBS_CODERATE_UNDEFINED = - Constants.FrontendIsdbsCoderate.UNDEFINED; - /** @hide */ - public static final int FRONTEND_ISDBS_CODERATE_AUTO = Constants.FrontendIsdbsCoderate.AUTO; - /** @hide */ - public static final int FRONTEND_ISDBS_CODERATE_1_2 = - Constants.FrontendIsdbsCoderate.CODERATE_1_2; - /** @hide */ - public static final int FRONTEND_ISDBS_CODERATE_2_3 = - Constants.FrontendIsdbsCoderate.CODERATE_2_3; - /** @hide */ - public static final int FRONTEND_ISDBS_CODERATE_3_4 = - Constants.FrontendIsdbsCoderate.CODERATE_3_4; - /** @hide */ - public static final int FRONTEND_ISDBS_CODERATE_5_6 = - Constants.FrontendIsdbsCoderate.CODERATE_5_6; - /** @hide */ - public static final int FRONTEND_ISDBS_CODERATE_7_8 = - Constants.FrontendIsdbsCoderate.CODERATE_7_8; - - /** @hide */ - @IntDef({FRONTEND_ISDBT_MODE_UNDEFINED, FRONTEND_ISDBT_MODE_AUTO, FRONTEND_ISDBT_MODE_1, - FRONTEND_ISDBT_MODE_2, FRONTEND_ISDBT_MODE_3}) - @Retention(RetentionPolicy.SOURCE) - public @interface FrontendIsdbtMode {} - /** @hide */ - public static final int FRONTEND_ISDBT_MODE_UNDEFINED = Constants.FrontendIsdbtMode.UNDEFINED; - /** @hide */ - public static final int FRONTEND_ISDBT_MODE_AUTO = Constants.FrontendIsdbtMode.AUTO; - /** @hide */ - public static final int FRONTEND_ISDBT_MODE_1 = Constants.FrontendIsdbtMode.MODE_1; - /** @hide */ - public static final int FRONTEND_ISDBT_MODE_2 = Constants.FrontendIsdbtMode.MODE_2; - /** @hide */ - public static final int FRONTEND_ISDBT_MODE_3 = Constants.FrontendIsdbtMode.MODE_3; - - /** @hide */ - @IntDef({FRONTEND_ISDBT_BANDWIDTH_UNDEFINED, FRONTEND_ISDBT_BANDWIDTH_AUTO, - FRONTEND_ISDBT_BANDWIDTH_8MHZ, FRONTEND_ISDBT_BANDWIDTH_7MHZ, - FRONTEND_ISDBT_BANDWIDTH_6MHZ}) - @Retention(RetentionPolicy.SOURCE) - public @interface FrontendIsdbtBandwidth {} - /** @hide */ - public static final int FRONTEND_ISDBT_BANDWIDTH_UNDEFINED = - Constants.FrontendIsdbtBandwidth.UNDEFINED; - /** @hide */ - public static final int FRONTEND_ISDBT_BANDWIDTH_AUTO = Constants.FrontendIsdbtBandwidth.AUTO; - /** @hide */ - public static final int FRONTEND_ISDBT_BANDWIDTH_8MHZ = - Constants.FrontendIsdbtBandwidth.BANDWIDTH_8MHZ; - /** @hide */ - public static final int FRONTEND_ISDBT_BANDWIDTH_7MHZ = - Constants.FrontendIsdbtBandwidth.BANDWIDTH_7MHZ; - /** @hide */ - public static final int FRONTEND_ISDBT_BANDWIDTH_6MHZ = - Constants.FrontendIsdbtBandwidth.BANDWIDTH_6MHZ; - - /** @hide */ @IntDef({FILTER_SETTINGS_TS, FILTER_SETTINGS_MMTP, FILTER_SETTINGS_IP, FILTER_SETTINGS_TLV, FILTER_SETTINGS_ALP}) @Retention(RetentionPolicy.SOURCE) diff --git a/media/java/android/media/tv/tuner/dvr/Dvr.java b/media/java/android/media/tv/tuner/dvr/Dvr.java index f90042b8e745..95508d3b366d 100644 --- a/media/java/android/media/tv/tuner/dvr/Dvr.java +++ b/media/java/android/media/tv/tuner/dvr/Dvr.java @@ -18,7 +18,6 @@ package android.media.tv.tuner.dvr; import android.annotation.BytesLong; import android.annotation.NonNull; -import android.media.tv.tuner.Tuner.DvrCallback; import android.media.tv.tuner.Tuner.Filter; import android.media.tv.tuner.TunerConstants.Result; import android.os.ParcelFileDescriptor; diff --git a/media/java/android/media/tv/tuner/dvr/DvrCallback.java b/media/java/android/media/tv/tuner/dvr/DvrCallback.java new file mode 100644 index 000000000000..3d140f0accac --- /dev/null +++ b/media/java/android/media/tv/tuner/dvr/DvrCallback.java @@ -0,0 +1,33 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv.tuner.dvr; + +/** + * Callback interface for receiving information from DVR interfaces. + * + * @hide + */ +public interface DvrCallback { + /** + * Invoked when record status changed. + */ + void onRecordStatusChanged(int status); + /** + * Invoked when playback status changed. + */ + void onPlaybackStatusChanged(int status); +} diff --git a/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java index f0fe533093ba..fcca6a1615c3 100644 --- a/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java +++ b/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java @@ -16,20 +16,123 @@ package android.media.tv.tuner.filter; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.content.Context; +import android.hardware.tv.tuner.V1_0.Constants; +import android.media.tv.tuner.TunerUtils; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Filter configuration for a ALP filter. * @hide */ public class AlpFilterConfiguration extends FilterConfiguration { - private int mPacketType; - private int mLengthType; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "LENGTH_TYPE_", value = + {LENGTH_TYPE_UNDEFINED, LENGTH_TYPE_WITHOUT_ADDITIONAL_HEADER, + LENGTH_TYPE_WITH_ADDITIONAL_HEADER}) + public @interface LengthType {} + + /** + * Length type not defined. + */ + public static final int LENGTH_TYPE_UNDEFINED = Constants.DemuxAlpLengthType.UNDEFINED; + /** + * Length does NOT include additional header. + */ + public static final int LENGTH_TYPE_WITHOUT_ADDITIONAL_HEADER = + Constants.DemuxAlpLengthType.WITHOUT_ADDITIONAL_HEADER; + /** + * Length includes additional header. + */ + public static final int LENGTH_TYPE_WITH_ADDITIONAL_HEADER = + Constants.DemuxAlpLengthType.WITH_ADDITIONAL_HEADER; + - public AlpFilterConfiguration(Settings settings) { + private final int mPacketType; + private final int mLengthType; + + public AlpFilterConfiguration(Settings settings, int packetType, int lengthType) { super(settings); + mPacketType = packetType; + mLengthType = lengthType; } @Override public int getType() { return FilterConfiguration.FILTER_TYPE_ALP; } + + /** + * Gets packet type. + */ + @FilterConfiguration.PacketType + public int getPacketType() { + return mPacketType; + } + /** + * Gets length type. + */ + @LengthType + public int getLengthType() { + return mLengthType; + } + + /** + * Creates a builder for {@link AlpFilterConfiguration}. + * + * @param context the context of the caller. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @NonNull + public static Builder builder(@NonNull Context context) { + TunerUtils.checkTunerPermission(context); + return new Builder(); + } + + /** + * Builder for {@link AlpFilterConfiguration}. + */ + public static class Builder extends FilterConfiguration.Builder<Builder> { + private int mPacketType; + private int mLengthType; + + private Builder() { + } + + /** + * Sets packet type. + */ + @NonNull + public Builder setPacketType(@FilterConfiguration.PacketType int packetType) { + mPacketType = packetType; + return this; + } + /** + * Sets length type. + */ + @NonNull + public Builder setLengthType(@LengthType int lengthType) { + mLengthType = lengthType; + return this; + } + + /** + * Builds a {@link AlpFilterConfiguration} object. + */ + @NonNull + public AlpFilterConfiguration build() { + return new AlpFilterConfiguration(mSettings, mPacketType, mLengthType); + } + + @Override + Builder self() { + return this; + } + } } diff --git a/media/java/android/media/tv/tuner/filter/AvSettings.java b/media/java/android/media/tv/tuner/filter/AvSettings.java index a7c49d5585f8..940b5ae44a74 100644 --- a/media/java/android/media/tv/tuner/filter/AvSettings.java +++ b/media/java/android/media/tv/tuner/filter/AvSettings.java @@ -16,21 +16,84 @@ package android.media.tv.tuner.filter; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.content.Context; import android.media.tv.tuner.TunerConstants; import android.media.tv.tuner.TunerUtils; +import android.media.tv.tuner.filter.FilterConfiguration.FilterType; /** * Filter Settings for a Video and Audio. + * * @hide */ public class AvSettings extends Settings { - private boolean mIsPassthrough; + private final boolean mIsPassthrough; - private AvSettings(int mainType, boolean isAudio) { + private AvSettings(int mainType, boolean isAudio, boolean isPassthrough) { super(TunerUtils.getFilterSubtype( mainType, isAudio ? TunerConstants.FILTER_SUBTYPE_AUDIO : TunerConstants.FILTER_SUBTYPE_VIDEO)); + mIsPassthrough = isPassthrough; + } + + /** + * Checks whether it's passthrough. + */ + public boolean isPassthrough() { + return mIsPassthrough; + } + + /** + * Creates a builder for {@link AvSettings}. + * + * @param context the context of the caller. + * @param mainType the filter main type. + * @param isAudio {@code true} if it's audio settings; {@code false} if it's video settings. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @NonNull + public static Builder builder( + @NonNull Context context, @FilterType int mainType, boolean isAudio) { + TunerUtils.checkTunerPermission(context); + return new Builder(mainType, isAudio); + } + + /** + * Builder for {@link AvSettings}. + */ + public static class Builder extends Settings.Builder<Builder> { + private final boolean mIsAudio; + private boolean mIsPassthrough; + + private Builder(int mainType, boolean isAudio) { + super(mainType); + mIsAudio = isAudio; + } + + /** + * Sets whether it's passthrough. + */ + @NonNull + public Builder setPassthrough(boolean isPassthrough) { + mIsPassthrough = isPassthrough; + return this; + } + + /** + * Builds a {@link AvSettings} object. + */ + @NonNull + public AvSettings build() { + return new AvSettings(mMainType, mIsAudio, mIsPassthrough); + } + + @Override + Builder self() { + return this; + } } } diff --git a/media/java/android/media/tv/tuner/filter/DownloadSettings.java b/media/java/android/media/tv/tuner/filter/DownloadSettings.java index 0742b1166ede..e3e1df064238 100644 --- a/media/java/android/media/tv/tuner/filter/DownloadSettings.java +++ b/media/java/android/media/tv/tuner/filter/DownloadSettings.java @@ -16,17 +16,75 @@ package android.media.tv.tuner.filter; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.content.Context; import android.media.tv.tuner.TunerConstants; import android.media.tv.tuner.TunerUtils; +import android.media.tv.tuner.filter.FilterConfiguration.FilterType; /** * Filter Settings for a Download. * @hide */ public class DownloadSettings extends Settings { - private int mDownloadId; + private final int mDownloadId; - public DownloadSettings(int mainType) { + private DownloadSettings(int mainType, int downloadId) { super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_DOWNLOAD)); + mDownloadId = downloadId; + } + + /** + * Gets download ID. + */ + public int getDownloadId() { + return mDownloadId; + } + + /** + * Creates a builder for {@link DownloadSettings}. + * + * @param context the context of the caller. + * @param mainType the filter main type. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @NonNull + public static Builder builder(@NonNull Context context, @FilterType int mainType) { + TunerUtils.checkTunerPermission(context); + return new Builder(mainType); + } + + /** + * Builder for {@link DownloadSettings}. + */ + public static class Builder extends Settings.Builder<Builder> { + private int mDownloadId; + + private Builder(int mainType) { + super(mainType); + } + + /** + * Sets download ID. + */ + @NonNull + public Builder setDownloadId(int downloadId) { + mDownloadId = downloadId; + return this; + } + + /** + * Builds a {@link DownloadSettings} object. + */ + @NonNull + public DownloadSettings build() { + return new DownloadSettings(mMainType, mDownloadId); + } + + @Override + Builder self() { + return this; + } } } diff --git a/media/java/android/media/tv/tuner/filter/FilterCallback.java b/media/java/android/media/tv/tuner/filter/FilterCallback.java new file mode 100644 index 000000000000..888adc5f51bb --- /dev/null +++ b/media/java/android/media/tv/tuner/filter/FilterCallback.java @@ -0,0 +1,42 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv.tuner.filter; + +import android.annotation.NonNull; +import android.media.tv.tuner.TunerConstants.FilterStatus; + +/** + * Callback interface for receiving information from the corresponding filters. + * + * @hide + */ +public interface FilterCallback { + /** + * Invoked when there are filter events. + * + * @param filter the corresponding filter which sent the events. + * @param events the filter events sent from the filter. + */ + void onFilterEvent(@NonNull Filter filter, @NonNull FilterEvent[] events); + /** + * Invoked when filter status changed. + * + * @param filter the corresponding filter whose status is changed. + * @param status the new status of the filter. + */ + void onFilterStatusChanged(@NonNull Filter filter, @FilterStatus int status); +} diff --git a/media/java/android/media/tv/tuner/filter/FilterConfiguration.java b/media/java/android/media/tv/tuner/filter/FilterConfiguration.java index 99b10cde34f9..68c722f244d4 100644 --- a/media/java/android/media/tv/tuner/filter/FilterConfiguration.java +++ b/media/java/android/media/tv/tuner/filter/FilterConfiguration.java @@ -18,6 +18,7 @@ package android.media.tv.tuner.filter; import android.annotation.IntDef; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.hardware.tv.tuner.V1_0.Constants; import java.lang.annotation.Retention; @@ -28,10 +29,12 @@ import java.lang.annotation.RetentionPolicy; * * @hide */ +@SystemApi public abstract class FilterConfiguration { /** @hide */ - @IntDef({FILTER_TYPE_TS, FILTER_TYPE_MMTP, FILTER_TYPE_IP, FILTER_TYPE_TLV, FILTER_TYPE_ALP}) + @IntDef(prefix = "FILTER_TYPE_", value = + {FILTER_TYPE_TS, FILTER_TYPE_MMTP, FILTER_TYPE_IP, FILTER_TYPE_TLV, FILTER_TYPE_ALP}) @Retention(RetentionPolicy.SOURCE) public @interface FilterType {} @@ -56,8 +59,32 @@ public abstract class FilterConfiguration { */ public static final int FILTER_TYPE_ALP = Constants.DemuxFilterMainType.ALP; + + /** @hide */ + @IntDef(prefix = "PACKET_TYPE_", value = + {PACKET_TYPE_IPV4, PACKET_TYPE_COMPRESSED, PACKET_TYPE_SIGNALING}) + @Retention(RetentionPolicy.SOURCE) + public @interface PacketType {} + + /** + * IP v4 packet type. + * @hide + */ + public static final int PACKET_TYPE_IPV4 = 0; + /** + * Compressed packet type. + * @hide + */ + public static final int PACKET_TYPE_COMPRESSED = 2; + /** + * Signaling packet type. + * @hide + */ + public static final int PACKET_TYPE_SIGNALING = 4; + + @Nullable - private final Settings mSettings; + /* package */ final Settings mSettings; /* package */ FilterConfiguration(Settings settings) { mSettings = settings; @@ -75,4 +102,27 @@ public abstract class FilterConfiguration { public Settings getSettings() { return mSettings; } + + /** + * Builder for {@link FilterConfiguration}. + * + * @param <T> The subclass to be built. + * @hide + */ + public abstract static class Builder<T extends Builder<T>> { + /* package */ Settings mSettings; + + /* package */ Builder() { + } + + /** + * Sets filter settings. + */ + @Nullable + public T setFrequency(Settings settings) { + mSettings = settings; + return self(); + } + /* package */ abstract T self(); + } } diff --git a/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java index c89636887628..98edf1035df3 100644 --- a/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java +++ b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java @@ -16,23 +16,152 @@ package android.media.tv.tuner.filter; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.Size; +import android.content.Context; +import android.media.tv.tuner.TunerUtils; + /** * Filter configuration for a IP filter. * @hide */ public class IpFilterConfiguration extends FilterConfiguration { - private byte[] mSrcIpAddress; - private byte[] mDstIpAddress; - private int mSrcPort; - private int mDstPort; - private boolean mPassthrough; + private final byte[] mSrcIpAddress; + private final byte[] mDstIpAddress; + private final int mSrcPort; + private final int mDstPort; + private final boolean mPassthrough; - public IpFilterConfiguration(Settings settings) { + public IpFilterConfiguration(Settings settings, byte[] srcAddr, byte[] dstAddr, int srcPort, + int dstPort, boolean passthrough) { super(settings); + mSrcIpAddress = srcAddr; + mDstIpAddress = dstAddr; + mSrcPort = srcPort; + mDstPort = dstPort; + mPassthrough = passthrough; } @Override public int getType() { return FilterConfiguration.FILTER_TYPE_IP; } + + /** + * Gets source IP address. + */ + @Size(min = 4, max = 16) + public byte[] getSrcIpAddress() { + return mSrcIpAddress; + } + /** + * Gets destination IP address. + */ + @Size(min = 4, max = 16) + public byte[] getDstIpAddress() { + return mDstIpAddress; + } + /** + * Gets source port. + */ + public int getSrcPort() { + return mSrcPort; + } + /** + * Gets destination port. + */ + public int getDstPort() { + return mDstPort; + } + /** + * Checks whether the filter is passthrough. + * + * @return {@code true} if the data from IP subtype go to next filter directly; + * {@code false} otherwise. + */ + public boolean isPassthrough() { + return mPassthrough; + } + + /** + * Creates a builder for {@link IpFilterConfiguration}. + * + * @param context the context of the caller. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @NonNull + public static Builder builder(@NonNull Context context) { + TunerUtils.checkTunerPermission(context); + return new Builder(); + } + + /** + * Builder for {@link IpFilterConfiguration}. + */ + public static class Builder extends FilterConfiguration.Builder<Builder> { + private byte[] mSrcIpAddress; + private byte[] mDstIpAddress; + private int mSrcPort; + private int mDstPort; + private boolean mPassthrough; + + private Builder() { + } + + /** + * Sets source IP address. + */ + @NonNull + public Builder setSrcIpAddress(byte[] srcIpAddress) { + mSrcIpAddress = srcIpAddress; + return this; + } + /** + * Sets destination IP address. + */ + @NonNull + public Builder setDstIpAddress(byte[] dstIpAddress) { + mDstIpAddress = dstIpAddress; + return this; + } + /** + * Sets source port. + */ + @NonNull + public Builder setSrcPort(int srcPort) { + mSrcPort = srcPort; + return this; + } + /** + * Sets destination port. + */ + @NonNull + public Builder setDstPort(int dstPort) { + mDstPort = dstPort; + return this; + } + /** + * Sets passthrough. + */ + @NonNull + public Builder setPassthrough(boolean passthrough) { + mPassthrough = passthrough; + return this; + } + + /** + * Builds a {@link IpFilterConfiguration} object. + */ + @NonNull + public IpFilterConfiguration build() { + return new IpFilterConfiguration( + mSettings, mSrcIpAddress, mDstIpAddress, mSrcPort, mDstPort, mPassthrough); + } + + @Override + Builder self() { + return this; + } + } } diff --git a/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java index 9045ce67a61f..83246e51c8b6 100644 --- a/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java +++ b/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java @@ -16,19 +16,78 @@ package android.media.tv.tuner.filter; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.content.Context; +import android.media.tv.tuner.TunerUtils; + /** * Filter configuration for a MMTP filter. * @hide */ public class MmtpFilterConfiguration extends FilterConfiguration { - private int mMmtpPid; + private final int mMmtpPid; - public MmtpFilterConfiguration(Settings settings) { + public MmtpFilterConfiguration(Settings settings, int mmtpPid) { super(settings); + mMmtpPid = mmtpPid; } @Override public int getType() { return FilterConfiguration.FILTER_TYPE_MMTP; } + + /** + * Gets MMPT PID. + * + * <p>Packet ID is used to specify packets in MMTP. + */ + public int getMmtpPid() { + return mMmtpPid; + } + + /** + * Creates a builder for {@link IpFilterConfiguration}. + * + * @param context the context of the caller. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @NonNull + public static Builder builder(@NonNull Context context) { + TunerUtils.checkTunerPermission(context); + return new Builder(); + } + + /** + * Builder for {@link IpFilterConfiguration}. + */ + public static class Builder extends FilterConfiguration.Builder<Builder> { + private int mMmtpPid; + + private Builder() { + } + + /** + * Sets MMPT PID. + */ + @NonNull + public Builder setMmtpPid(int mmtpPid) { + mMmtpPid = mmtpPid; + return this; + } + + /** + * Builds a {@link IpFilterConfiguration} object. + */ + @NonNull + public MmtpFilterConfiguration build() { + return new MmtpFilterConfiguration(mSettings, mMmtpPid); + } + + @Override + Builder self() { + return this; + } + } } diff --git a/media/java/android/media/tv/tuner/filter/PesSettings.java b/media/java/android/media/tv/tuner/filter/PesSettings.java index f38abf12e120..bfa1f8c67d97 100644 --- a/media/java/android/media/tv/tuner/filter/PesSettings.java +++ b/media/java/android/media/tv/tuner/filter/PesSettings.java @@ -17,6 +17,9 @@ package android.media.tv.tuner.filter; import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.content.Context; import android.media.tv.tuner.TunerConstants; import android.media.tv.tuner.TunerUtils; import android.media.tv.tuner.filter.FilterConfiguration.FilterType; @@ -26,6 +29,7 @@ import android.media.tv.tuner.filter.FilterConfiguration.FilterType; * * @hide */ +@SystemApi public class PesSettings extends Settings { private final int mStreamId; private final boolean mIsRaw; @@ -37,12 +41,32 @@ public class PesSettings extends Settings { } /** + * Gets stream ID. + */ + public int getStreamId() { + return mStreamId; + } + + /** + * Returns whether the data is raw. + * + * @return {@code true} if the data is raw. Filter sends onFilterStatus callback + * instead of onFilterEvent for raw data. {@code false} otherwise. + */ + public boolean isRaw() { + return mIsRaw; + } + + /** * Creates a builder for {@link PesSettings}. * * @param mainType the filter main type of the settings. + * @param context the context of the caller. */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) @NonNull - public static Builder newBuilder(@FilterType int mainType) { + public static Builder builder(@NonNull Context context, @FilterType int mainType) { + TunerUtils.checkTunerPermission(context); return new Builder(mainType); } @@ -70,13 +94,13 @@ public class PesSettings extends Settings { } /** - * Sets whether it's raw. + * Sets whether the data is raw. * * @param isRaw {@code true} if the data is raw. Filter sends onFilterStatus callback * instead of onFilterEvent for raw data. {@code false} otherwise. */ @NonNull - public Builder setIsRaw(boolean isRaw) { + public Builder setRaw(boolean isRaw) { mIsRaw = isRaw; return this; } diff --git a/media/java/android/media/tv/tuner/filter/RecordSettings.java b/media/java/android/media/tv/tuner/filter/RecordSettings.java index 701868afc789..483370935352 100644 --- a/media/java/android/media/tv/tuner/filter/RecordSettings.java +++ b/media/java/android/media/tv/tuner/filter/RecordSettings.java @@ -16,18 +16,231 @@ package android.media.tv.tuner.filter; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.content.Context; +import android.hardware.tv.tuner.V1_0.Constants; import android.media.tv.tuner.TunerConstants; +import android.media.tv.tuner.TunerConstants.ScIndexType; import android.media.tv.tuner.TunerUtils; +import android.media.tv.tuner.filter.FilterConfiguration.FilterType; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * The Settings for the record in DVR. * @hide */ public class RecordSettings extends Settings { - private int mIndexType; - private int mIndexMask; + /** + * Indexes can be tagged through TS (Transport Stream) header. + * + * @hide + */ + @IntDef(flag = true, + prefix = "TS_INDEX_", + value = {TS_INDEX_FIRST_PACKET, TS_INDEX_PAYLOAD_UNIT_START_INDICATOR, + TS_INDEX_CHANGE_TO_NOT_SCRAMBLED, TS_INDEX_CHANGE_TO_EVEN_SCRAMBLED, + TS_INDEX_CHANGE_TO_ODD_SCRAMBLED, TS_INDEX_DISCONTINUITY_INDICATOR, + TS_INDEX_RANDOM_ACCESS_INDICATOR, TS_INDEX_PRIORITY_INDICATOR, + TS_INDEX_PCR_FLAG, TS_INDEX_OPCR_FLAG, TS_INDEX_SPLICING_POINT_FLAG, + TS_INDEX_PRIVATE_DATA, TS_INDEX_ADAPTATION_EXTENSION_FLAG}) + @Retention(RetentionPolicy.SOURCE) + public @interface TsIndexMask {} + + /** + * TS index FIRST_PACKET. + * @hide + */ + public static final int TS_INDEX_FIRST_PACKET = Constants.DemuxTsIndex.FIRST_PACKET; + /** + * TS index PAYLOAD_UNIT_START_INDICATOR. + * @hide + */ + public static final int TS_INDEX_PAYLOAD_UNIT_START_INDICATOR = + Constants.DemuxTsIndex.PAYLOAD_UNIT_START_INDICATOR; + /** + * TS index CHANGE_TO_NOT_SCRAMBLED. + * @hide + */ + public static final int TS_INDEX_CHANGE_TO_NOT_SCRAMBLED = + Constants.DemuxTsIndex.CHANGE_TO_NOT_SCRAMBLED; + /** + * TS index CHANGE_TO_EVEN_SCRAMBLED. + * @hide + */ + public static final int TS_INDEX_CHANGE_TO_EVEN_SCRAMBLED = + Constants.DemuxTsIndex.CHANGE_TO_EVEN_SCRAMBLED; + /** + * TS index CHANGE_TO_ODD_SCRAMBLED. + * @hide + */ + public static final int TS_INDEX_CHANGE_TO_ODD_SCRAMBLED = + Constants.DemuxTsIndex.CHANGE_TO_ODD_SCRAMBLED; + /** + * TS index DISCONTINUITY_INDICATOR. + * @hide + */ + public static final int TS_INDEX_DISCONTINUITY_INDICATOR = + Constants.DemuxTsIndex.DISCONTINUITY_INDICATOR; + /** + * TS index RANDOM_ACCESS_INDICATOR. + * @hide + */ + public static final int TS_INDEX_RANDOM_ACCESS_INDICATOR = + Constants.DemuxTsIndex.RANDOM_ACCESS_INDICATOR; + /** + * TS index PRIORITY_INDICATOR. + * @hide + */ + public static final int TS_INDEX_PRIORITY_INDICATOR = Constants.DemuxTsIndex.PRIORITY_INDICATOR; + /** + * TS index PCR_FLAG. + * @hide + */ + public static final int TS_INDEX_PCR_FLAG = Constants.DemuxTsIndex.PCR_FLAG; + /** + * TS index OPCR_FLAG. + * @hide + */ + public static final int TS_INDEX_OPCR_FLAG = Constants.DemuxTsIndex.OPCR_FLAG; + /** + * TS index SPLICING_POINT_FLAG. + * @hide + */ + public static final int TS_INDEX_SPLICING_POINT_FLAG = + Constants.DemuxTsIndex.SPLICING_POINT_FLAG; + /** + * TS index PRIVATE_DATA. + * @hide + */ + public static final int TS_INDEX_PRIVATE_DATA = Constants.DemuxTsIndex.PRIVATE_DATA; + /** + * TS index ADAPTATION_EXTENSION_FLAG. + * @hide + */ + public static final int TS_INDEX_ADAPTATION_EXTENSION_FLAG = + Constants.DemuxTsIndex.ADAPTATION_EXTENSION_FLAG; + /** + * @hide + */ + @IntDef(flag = true, + prefix = "SC_", + value = { + TunerConstants.SC_INDEX_I_FRAME, + TunerConstants.SC_INDEX_P_FRAME, + TunerConstants.SC_INDEX_B_FRAME, + TunerConstants.SC_INDEX_SEQUENCE, + TunerConstants.SC_HEVC_INDEX_SPS, + TunerConstants.SC_HEVC_INDEX_AUD, + TunerConstants.SC_HEVC_INDEX_SLICE_CE_BLA_W_LP, + TunerConstants.SC_HEVC_INDEX_SLICE_BLA_W_RADL, + TunerConstants.SC_HEVC_INDEX_SLICE_BLA_N_LP, + TunerConstants.SC_HEVC_INDEX_SLICE_IDR_W_RADL, + TunerConstants.SC_HEVC_INDEX_SLICE_IDR_N_LP, + TunerConstants.SC_HEVC_INDEX_SLICE_TRAIL_CRA, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ScIndexMask {} + - public RecordSettings(int mainType) { + private final int mTsIndexMask; + private final int mScIndexType; + private final int mScIndexMask; + + private RecordSettings(int mainType, int tsIndexType, int scIndexType, int scIndexMask) { super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_RECORD)); + mTsIndexMask = tsIndexType; + mScIndexType = scIndexType; + mScIndexMask = scIndexMask; } + + /** + * Gets TS index mask. + */ + @TsIndexMask + public int getTsIndexMask() { + return mTsIndexMask; + } + /** + * Gets Start Code index type. + */ + @ScIndexType + public int getScIndexType() { + return mScIndexType; + } + /** + * Gets Start Code index mask. + */ + @ScIndexMask + public int getScIndexMask() { + return mScIndexMask; + } + + /** + * Creates a builder for {@link RecordSettings}. + * + * @param context the context of the caller. + * @param mainType the filter main type. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @NonNull + public static Builder builder(@NonNull Context context, @FilterType int mainType) { + TunerUtils.checkTunerPermission(context); + return new Builder(mainType); + } + + /** + * Builder for {@link RecordSettings}. + */ + public static class Builder extends Settings.Builder<Builder> { + private int mTsIndexMask; + private int mScIndexType; + private int mScIndexMask; + + private Builder(int mainType) { + super(mainType); + } + + /** + * Sets TS index mask. + */ + @NonNull + public Builder setTsIndexMask(@TsIndexMask int indexMask) { + mTsIndexMask = indexMask; + return this; + } + /** + * Sets index type. + */ + @NonNull + public Builder setScIndexType(@ScIndexType int indexType) { + mScIndexType = indexType; + return this; + } + /** + * Sets Start Code index mask. + */ + @NonNull + public Builder setScIndexMask(@ScIndexMask int indexMask) { + mScIndexMask = indexMask; + return this; + } + + /** + * Builds a {@link RecordSettings} object. + */ + @NonNull + public RecordSettings build() { + return new RecordSettings(mMainType, mTsIndexMask, mScIndexType, mScIndexMask); + } + + @Override + Builder self() { + return this; + } + } + } diff --git a/media/java/android/media/tv/tuner/filter/SectionSettingsWithSectionBits.java b/media/java/android/media/tv/tuner/filter/SectionSettingsWithSectionBits.java index 414ea6790bf5..0fa982e3dd01 100644 --- a/media/java/android/media/tv/tuner/filter/SectionSettingsWithSectionBits.java +++ b/media/java/android/media/tv/tuner/filter/SectionSettingsWithSectionBits.java @@ -16,18 +16,116 @@ package android.media.tv.tuner.filter; -import java.util.List; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.content.Context; +import android.media.tv.tuner.TunerUtils; +import android.media.tv.tuner.filter.FilterConfiguration.FilterType; /** - * Bits Settings for Section Filter. + * Bits Settings for Section Filters. * @hide */ public class SectionSettingsWithSectionBits extends SectionSettings { - private List<Byte> mFilter; - private List<Byte> mMask; - private List<Byte> mMode; + private final byte[] mFilter; + private final byte[] mMask; + private final byte[] mMode; - private SectionSettingsWithSectionBits(int mainType) { + + private SectionSettingsWithSectionBits(int mainType, byte[] filter, byte[] mask, byte[] mode) { super(mainType); + mFilter = filter; + mMask = mask; + mMode = mode; + } + + /** + * Gets the bytes configured for Section Filter + */ + public byte[] getFilterBytes() { + return mFilter; + } + /** + * Gets bit mask. + * + * <p>The bits in the bytes are used for filtering. + */ + public byte[] getMask() { + return mMask; + } + /** + * Gets mode. + * + * <p>Do positive match at the bit position of the configured bytes when the bit at same + * position of the mode is 0. + * <p>Do negative match at the bit position of the configured bytes when the bit at same + * position of the mode is 1. + */ + public byte[] getMode() { + return mMode; + } + + /** + * Creates a builder for {@link SectionSettingsWithSectionBits}. + * + * @param context the context of the caller. + * @param mainType the filter main type. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @NonNull + public static Builder builder(@NonNull Context context, @FilterType int mainType) { + TunerUtils.checkTunerPermission(context); + return new Builder(mainType); + } + + /** + * Builder for {@link SectionSettingsWithSectionBits}. + */ + public static class Builder extends Settings.Builder<Builder> { + private byte[] mFilter; + private byte[] mMask; + private byte[] mMode; + + private Builder(int mainType) { + super(mainType); + } + + /** + * Sets filter bytes. + */ + @NonNull + public Builder setFilter(byte[] filter) { + mFilter = filter; + return this; + } + /** + * Sets bit mask. + */ + @NonNull + public Builder setMask(byte[] mask) { + mMask = mask; + return this; + } + /** + * Sets mode. + */ + @NonNull + public Builder setMode(byte[] mode) { + mMode = mode; + return this; + } + + /** + * Builds a {@link SectionSettingsWithSectionBits} object. + */ + @NonNull + public SectionSettingsWithSectionBits build() { + return new SectionSettingsWithSectionBits(mMainType, mFilter, mMask, mMode); + } + + @Override + Builder self() { + return this; + } } } diff --git a/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java b/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java index 0df1d7308e60..6542b89478b2 100644 --- a/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java +++ b/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java @@ -16,15 +16,92 @@ package android.media.tv.tuner.filter; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.content.Context; +import android.media.tv.tuner.TunerUtils; +import android.media.tv.tuner.filter.FilterConfiguration.FilterType; + /** * Table information for Section Filter. * @hide */ public class SectionSettingsWithTableInfo extends SectionSettings { - private int mTableId; - private int mVersion; + private final int mTableId; + private final int mVersion; - private SectionSettingsWithTableInfo(int mainType) { + private SectionSettingsWithTableInfo(int mainType, int tableId, int version) { super(mainType); + mTableId = tableId; + mVersion = version; + } + + /** + * Gets table ID. + */ + public int getTableId() { + return mTableId; + } + /** + * Gets version. + */ + public int getVersion() { + return mVersion; } + + /** + * Creates a builder for {@link SectionSettingsWithTableInfo}. + * + * @param context the context of the caller. + * @param mainType the filter main type. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @NonNull + public static Builder builder(@NonNull Context context, @FilterType int mainType) { + TunerUtils.checkTunerPermission(context); + return new Builder(mainType); + } + + /** + * Builder for {@link SectionSettingsWithTableInfo}. + */ + public static class Builder extends Settings.Builder<Builder> { + private int mTableId; + private int mVersion; + + private Builder(int mainType) { + super(mainType); + } + + /** + * Sets table ID. + */ + @NonNull + public Builder setTableId(int tableId) { + mTableId = tableId; + return this; + } + /** + * Sets version. + */ + @NonNull + public Builder setVersion(int version) { + mVersion = version; + return this; + } + + /** + * Builds a {@link SectionSettingsWithTableInfo} object. + */ + @NonNull + public SectionSettingsWithTableInfo build() { + return new SectionSettingsWithTableInfo(mMainType, mTableId, mVersion); + } + + @Override + Builder self() { + return this; + } + } + } diff --git a/media/java/android/media/tv/tuner/filter/Settings.java b/media/java/android/media/tv/tuner/filter/Settings.java index 146aca74ce0e..d697280a4106 100644 --- a/media/java/android/media/tv/tuner/filter/Settings.java +++ b/media/java/android/media/tv/tuner/filter/Settings.java @@ -16,11 +16,14 @@ package android.media.tv.tuner.filter; +import android.annotation.SystemApi; + /** * Settings for filters of different subtypes. * * @hide */ +@SystemApi public abstract class Settings { private final int mType; @@ -36,4 +39,20 @@ public abstract class Settings { public int getType() { return mType; } + + + /** + * Builder for {@link Settings}. + * + * @param <T> The subclass to be built. + * @hide + */ + public abstract static class Builder<T extends Builder<T>> { + /* package */ final int mMainType; + + /* package */ Builder(int mainType) { + mMainType = mainType; + } + /* package */ abstract T self(); + } } diff --git a/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java index de8ee754a28c..eb97fc04362c 100644 --- a/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java +++ b/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java @@ -16,21 +16,118 @@ package android.media.tv.tuner.filter; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.content.Context; +import android.media.tv.tuner.TunerUtils; + /** * Filter configuration for a TLV filter. * @hide */ public class TlvFilterConfiguration extends FilterConfiguration { - private int mPacketType; - private boolean mIsCompressedIpPacket; - private boolean mPassthrough; + private final int mPacketType; + private final boolean mIsCompressedIpPacket; + private final boolean mPassthrough; - public TlvFilterConfiguration(Settings settings) { + public TlvFilterConfiguration(Settings settings, int packetType, boolean isCompressed, + boolean passthrough) { super(settings); + mPacketType = packetType; + mIsCompressedIpPacket = isCompressed; + mPassthrough = passthrough; } @Override public int getType() { return FilterConfiguration.FILTER_TYPE_TLV; } + + /** + * Gets packet type. + */ + @FilterConfiguration.PacketType + public int getPacketType() { + return mPacketType; + } + /** + * Checks whether the data is compressed IP packet. + * + * @return {@code true} if the filtered data is compressed IP packet; {@code false} otherwise. + */ + public boolean isCompressedIpPacket() { + return mIsCompressedIpPacket; + } + /** + * Checks whether it's passthrough. + * + * @return {@code true} if the data from TLV subtype go to next filter directly; + * {@code false} otherwise. + */ + public boolean isPassthrough() { + return mPassthrough; + } + + /** + * Creates a builder for {@link TlvFilterConfiguration}. + * + * @param context the context of the caller. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @NonNull + public static Builder builder(@NonNull Context context) { + TunerUtils.checkTunerPermission(context); + return new Builder(); + } + + /** + * Builder for {@link TlvFilterConfiguration}. + */ + public static class Builder extends FilterConfiguration.Builder<Builder> { + private int mPacketType; + private boolean mIsCompressedIpPacket; + private boolean mPassthrough; + + private Builder() { + } + + /** + * Sets packet type. + */ + @NonNull + public Builder setPacketType(@FilterConfiguration.PacketType int packetType) { + mPacketType = packetType; + return this; + } + /** + * Sets whether the data is compressed IP packet. + */ + @NonNull + public Builder setIsCompressedIpPacket(boolean isCompressedIpPacket) { + mIsCompressedIpPacket = isCompressedIpPacket; + return this; + } + /** + * Sets whether it's passthrough. + */ + @NonNull + public Builder setPassthrough(boolean passthrough) { + mPassthrough = passthrough; + return this; + } + + /** + * Builds a {@link TlvFilterConfiguration} object. + */ + @NonNull + public TlvFilterConfiguration build() { + return new TlvFilterConfiguration( + mSettings, mPacketType, mIsCompressedIpPacket, mPassthrough); + } + + @Override + Builder self() { + return this; + } + } } diff --git a/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java index d0241b6aba09..5c38cfa70eb3 100644 --- a/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java +++ b/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java @@ -17,12 +17,18 @@ package android.media.tv.tuner.filter; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.content.Context; +import android.media.tv.tuner.TunerUtils; /** * Filter configuration for a TS filter. * * @hide */ +@SystemApi public class TsFilterConfiguration extends FilterConfiguration { private final int mTpid; @@ -37,10 +43,28 @@ public class TsFilterConfiguration extends FilterConfiguration { } /** + * Gets the {@link Settings} object of this filter configuration. + */ + @Nullable + public Settings getSettings() { + return mSettings; + } + /** + * Gets Tag Protocol ID. + */ + public int getTpid() { + return mTpid; + } + + /** * Creates a builder for {@link TsFilterConfiguration}. + * + * @param context the context of the caller. */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) @NonNull - public static Builder newBuilder() { + public static Builder builder(@NonNull Context context) { + TunerUtils.checkTunerPermission(context); return new Builder(); } @@ -51,6 +75,9 @@ public class TsFilterConfiguration extends FilterConfiguration { private Settings mSettings; private int mTpid; + private Builder() { + } + /** * Sets filter settings. * diff --git a/media/java/android/media/tv/tuner/filter/TsRecordEvent.java b/media/java/android/media/tv/tuner/filter/TsRecordEvent.java index fa4dd72e3eda..1b8485e22dcf 100644 --- a/media/java/android/media/tv/tuner/filter/TsRecordEvent.java +++ b/media/java/android/media/tv/tuner/filter/TsRecordEvent.java @@ -16,12 +16,8 @@ package android.media.tv.tuner.filter; -import android.annotation.IntDef; import android.media.tv.tuner.Tuner.Filter; -import android.media.tv.tuner.TunerConstants; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; /** * Filter event sent from {@link Filter} objects for TS record data. @@ -29,47 +25,17 @@ import java.lang.annotation.RetentionPolicy; * @hide */ public class TsRecordEvent extends FilterEvent { - /** - * @hide - */ - @IntDef(flag = true, value = { - TunerConstants.TS_INDEX_FIRST_PACKET, - TunerConstants.TS_INDEX_PAYLOAD_UNIT_START_INDICATOR, - TunerConstants.TS_INDEX_CHANGE_TO_NOT_SCRAMBLED, - TunerConstants.TS_INDEX_CHANGE_TO_EVEN_SCRAMBLED, - TunerConstants.TS_INDEX_CHANGE_TO_ODD_SCRAMBLED, - TunerConstants.TS_INDEX_DISCONTINUITY_INDICATOR, - TunerConstants.TS_INDEX_RANDOM_ACCESS_INDICATOR, - TunerConstants.TS_INDEX_PRIORITY_INDICATOR, - TunerConstants.TS_INDEX_PCR_FLAG, - TunerConstants.TS_INDEX_OPCR_FLAG, - TunerConstants.TS_INDEX_SPLICING_POINT_FLAG, - TunerConstants.TS_INDEX_PRIVATE_DATA, - TunerConstants.TS_INDEX_ADAPTATION_EXTENSION_FLAG, - TunerConstants.SC_INDEX_I_FRAME, - TunerConstants.SC_INDEX_P_FRAME, - TunerConstants.SC_INDEX_B_FRAME, - TunerConstants.SC_INDEX_SEQUENCE, - TunerConstants.SC_HEVC_INDEX_SPS, - TunerConstants.SC_HEVC_INDEX_AUD, - TunerConstants.SC_HEVC_INDEX_SLICE_CE_BLA_W_LP, - TunerConstants.SC_HEVC_INDEX_SLICE_BLA_W_RADL, - TunerConstants.SC_HEVC_INDEX_SLICE_BLA_N_LP, - TunerConstants.SC_HEVC_INDEX_SLICE_IDR_W_RADL, - TunerConstants.SC_HEVC_INDEX_SLICE_IDR_N_LP, - TunerConstants.SC_HEVC_INDEX_SLICE_TRAIL_CRA, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface IndexMask {} private final int mPid; - private final int mIndexMask; + private final int mTsIndexMask; + private final int mScIndexMask; private final long mByteNumber; // This constructor is used by JNI code only - private TsRecordEvent(int pid, int indexMask, long byteNumber) { + private TsRecordEvent(int pid, int tsIndexMask, int scIndexMask, long byteNumber) { mPid = pid; - mIndexMask = indexMask; + mTsIndexMask = tsIndexMask; + mScIndexMask = scIndexMask; mByteNumber = byteNumber; } @@ -81,13 +47,20 @@ public class TsRecordEvent extends FilterEvent { } /** - * Gets index mask. + * Gets TS index mask. + */ + @RecordSettings.TsIndexMask + public int getTsIndexMask() { + return mTsIndexMask; + } + /** + * Gets SC index mask. * - * <p>The index type is one of TS, SC, and SC-HEVC, and is set when configuring the filter. + * <p>The index type is SC or SC-HEVC, and is set when configuring the filter. */ - @IndexMask - public int getIndexMask() { - return mIndexMask; + @RecordSettings.ScIndexMask + public int getScIndexMask() { + return mScIndexMask; } /** diff --git a/media/java/android/media/tv/tuner/frontend/AnalogFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/AnalogFrontendCapabilities.java index 2962e98790e5..aa64df56f556 100644 --- a/media/java/android/media/tv/tuner/frontend/AnalogFrontendCapabilities.java +++ b/media/java/android/media/tv/tuner/frontend/AnalogFrontendCapabilities.java @@ -17,24 +17,33 @@ package android.media.tv.tuner.frontend; /** - * Analog Capabilities. + * Capabilities for analog tuners. + * * @hide */ public class AnalogFrontendCapabilities extends FrontendCapabilities { + @AnalogFrontendSettings.SignalType private final int mTypeCap; + @AnalogFrontendSettings.SifStandard private final int mSifStandardCap; - AnalogFrontendCapabilities(int typeCap, int sifStandardCap) { + // Called by JNI code. + private AnalogFrontendCapabilities(int typeCap, int sifStandardCap) { mTypeCap = typeCap; mSifStandardCap = sifStandardCap; } + /** - * Gets type capability. + * Gets analog signal type capability. */ - public int getTypeCapability() { + @AnalogFrontendSettings.SignalType + public int getSignalTypeCapability() { return mTypeCap; } - /** Gets SIF standard capability. */ + /** + * Gets Standard Interchange Format (SIF) capability. + */ + @AnalogFrontendSettings.SifStandard public int getSifStandardCapability() { return mSifStandardCap; } diff --git a/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java index aec8ce895ab8..a30ddc73ac45 100644 --- a/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java +++ b/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java @@ -30,8 +30,9 @@ import java.lang.annotation.RetentionPolicy; */ public class AnalogFrontendSettings extends FrontendSettings { /** @hide */ - @IntDef(flag = true, value = {SIGNAL_TYPE_UNDEFINED, SIGNAL_TYPE_PAL, SIGNAL_TYPE_SECAM, - SIGNAL_TYPE_NTSC}) + @IntDef(flag = true, + prefix = "SIGNAL_TYPE_", + value = {SIGNAL_TYPE_UNDEFINED, SIGNAL_TYPE_PAL, SIGNAL_TYPE_SECAM, SIGNAL_TYPE_NTSC}) @Retention(RetentionPolicy.SOURCE) public @interface SignalType {} @@ -54,7 +55,9 @@ public class AnalogFrontendSettings extends FrontendSettings { /** @hide */ - @IntDef(flag = true, value = {SIF_UNDEFINED, SIF_BG, SIF_BG_A2, SIF_BG_NICAM, SIF_I, SIF_DK, + @IntDef(flag = true, + prefix = "SIF_", + value = {SIF_UNDEFINED, SIF_BG, SIF_BG_A2, SIF_BG_NICAM, SIF_I, SIF_DK, SIF_DK1, SIF_DK2, SIF_DK3, SIF_DK_NICAM, SIF_L, SIF_M, SIF_M_BTSC, SIF_M_A2, SIF_M_EIA_J, SIF_I_NICAM, SIF_L_NICAM, SIF_L_PRIME}) @Retention(RetentionPolicy.SOURCE) diff --git a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendCapabilities.java index 677f9387c6d2..1fd1f63c775a 100644 --- a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendCapabilities.java +++ b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendCapabilities.java @@ -28,8 +28,8 @@ public class Atsc3FrontendCapabilities extends FrontendCapabilities { private final int mFecCap; private final int mDemodOutputFormatCap; - Atsc3FrontendCapabilities(int bandwidthCap, int modulationCap, int timeInterleaveModeCap, - int codeRateCap, int fecCap, int demodOutputFormatCap) { + private Atsc3FrontendCapabilities(int bandwidthCap, int modulationCap, + int timeInterleaveModeCap, int codeRateCap, int fecCap, int demodOutputFormatCap) { mBandwidthCap = bandwidthCap; mModulationCap = modulationCap; mTimeInterleaveModeCap = timeInterleaveModeCap; @@ -38,27 +38,45 @@ public class Atsc3FrontendCapabilities extends FrontendCapabilities { mDemodOutputFormatCap = demodOutputFormatCap; } - /** Gets bandwidth capability. */ + /** + * Gets bandwidth capability. + */ + @Atsc3FrontendSettings.Bandwidth public int getBandwidthCapability() { return mBandwidthCap; } - /** Gets modulation capability. */ + /** + * Gets modulation capability. + */ + @Atsc3FrontendSettings.Modulation public int getModulationCapability() { return mModulationCap; } - /** Gets time interleave mod capability. */ + /** + * Gets time interleave mod capability. + */ + @Atsc3FrontendSettings.TimeInterleaveMode public int getTimeInterleaveModeCapability() { return mTimeInterleaveModeCap; } - /** Gets code rate capability. */ + /** + * Gets code rate capability. + */ + @Atsc3FrontendSettings.CodeRate public int getCodeRateCapability() { return mCodeRateCap; } - /** Gets FEC capability. */ + /** + * Gets FEC capability. + */ + @Atsc3FrontendSettings.Fec public int getFecCapability() { return mFecCap; } - /** Gets demodulator output format capability. */ + /** + * Gets demodulator output format capability. + */ + @Atsc3FrontendSettings.DemodOutputFormat public int getDemodOutputFormatCapability() { return mDemodOutputFormatCap; } diff --git a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java index 5b09e36ea113..5e1ba722c9fc 100644 --- a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java +++ b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java @@ -16,19 +16,357 @@ package android.media.tv.tuner.frontend; -import java.util.List; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.content.Context; +import android.hardware.tv.tuner.V1_0.Constants; +import android.media.tv.tuner.TunerUtils; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * Frontend settings for ATSC-3. * @hide */ public class Atsc3FrontendSettings extends FrontendSettings { - public int bandwidth; - public byte demodOutputFormat; - public List<Atsc3PlpSettings> plpSettings; - Atsc3FrontendSettings(int frequency) { + /** @hide */ + @IntDef(flag = true, + prefix = "BANDWIDTH_", + value = {BANDWIDTH_UNDEFINED, BANDWIDTH_AUTO, BANDWIDTH_BANDWIDTH_6MHZ, + BANDWIDTH_BANDWIDTH_7MHZ, BANDWIDTH_BANDWIDTH_8MHZ}) + @Retention(RetentionPolicy.SOURCE) + public @interface Bandwidth {} + + /** + * Bandwidth not defined. + */ + public static final int BANDWIDTH_UNDEFINED = + Constants.FrontendAtsc3Bandwidth.UNDEFINED; + /** + * Hardware is able to detect and set bandwidth automatically + */ + public static final int BANDWIDTH_AUTO = Constants.FrontendAtsc3Bandwidth.AUTO; + /** + * 6 MHz bandwidth. + */ + public static final int BANDWIDTH_BANDWIDTH_6MHZ = + Constants.FrontendAtsc3Bandwidth.BANDWIDTH_6MHZ; + /** + * 7 MHz bandwidth. + */ + public static final int BANDWIDTH_BANDWIDTH_7MHZ = + Constants.FrontendAtsc3Bandwidth.BANDWIDTH_7MHZ; + /** + * 8 MHz bandwidth. + */ + public static final int BANDWIDTH_BANDWIDTH_8MHZ = + Constants.FrontendAtsc3Bandwidth.BANDWIDTH_8MHZ; + + + /** @hide */ + @IntDef(flag = true, + prefix = "MODULATION_", + value = {MODULATION_UNDEFINED, MODULATION_AUTO, + MODULATION_MOD_QPSK, MODULATION_MOD_16QAM, + MODULATION_MOD_64QAM, MODULATION_MOD_256QAM, + MODULATION_MOD_1024QAM, MODULATION_MOD_4096QAM}) + @Retention(RetentionPolicy.SOURCE) + public @interface Modulation {} + + /** + * Modulation undefined. + */ + public static final int MODULATION_UNDEFINED = Constants.FrontendAtsc3Modulation.UNDEFINED; + /** + * Hardware is able to detect and set modulation automatically. + */ + public static final int MODULATION_AUTO = Constants.FrontendAtsc3Modulation.AUTO; + /** + * QPSK modulation. + */ + public static final int MODULATION_MOD_QPSK = Constants.FrontendAtsc3Modulation.MOD_QPSK; + /** + * 16QAM modulation. + */ + public static final int MODULATION_MOD_16QAM = Constants.FrontendAtsc3Modulation.MOD_16QAM; + /** + * 64QAM modulation. + */ + public static final int MODULATION_MOD_64QAM = Constants.FrontendAtsc3Modulation.MOD_64QAM; + /** + * 256QAM modulation. + */ + public static final int MODULATION_MOD_256QAM = Constants.FrontendAtsc3Modulation.MOD_256QAM; + /** + * 1024QAM modulation. + */ + public static final int MODULATION_MOD_1024QAM = Constants.FrontendAtsc3Modulation.MOD_1024QAM; + /** + * 4096QAM modulation. + */ + public static final int MODULATION_MOD_4096QAM = Constants.FrontendAtsc3Modulation.MOD_4096QAM; + + + /** @hide */ + @IntDef(flag = true, + prefix = "TIME_INTERLEAVE_MODE_", + value = {TIME_INTERLEAVE_MODE_UNDEFINED, TIME_INTERLEAVE_MODE_AUTO, + TIME_INTERLEAVE_MODE_CTI, TIME_INTERLEAVE_MODE_HTI}) + @Retention(RetentionPolicy.SOURCE) + public @interface TimeInterleaveMode {} + + /** + * Time interleave mode undefined. + */ + public static final int TIME_INTERLEAVE_MODE_UNDEFINED = + Constants.FrontendAtsc3TimeInterleaveMode.UNDEFINED; + /** + * Hardware is able to detect and set Time Interleave Mode automatically. + */ + public static final int TIME_INTERLEAVE_MODE_AUTO = + Constants.FrontendAtsc3TimeInterleaveMode.AUTO; + /** + * CTI Time Interleave Mode. + */ + public static final int TIME_INTERLEAVE_MODE_CTI = + Constants.FrontendAtsc3TimeInterleaveMode.CTI; + /** + * HTI Time Interleave Mode. + */ + public static final int TIME_INTERLEAVE_MODE_HTI = + Constants.FrontendAtsc3TimeInterleaveMode.HTI; + + + /** @hide */ + @IntDef(flag = true, + prefix = "CODERATE_", + value = {CODERATE_UNDEFINED, CODERATE_AUTO, CODERATE_2_15, CODERATE_3_15, CODERATE_4_15, + CODERATE_5_15, CODERATE_6_15, CODERATE_7_15, CODERATE_8_15, CODERATE_9_15, + CODERATE_10_15, CODERATE_11_15, CODERATE_12_15, CODERATE_13_15}) + @Retention(RetentionPolicy.SOURCE) + public @interface CodeRate {} + + /** + * Code rate undefined. + */ + public static final int CODERATE_UNDEFINED = Constants.FrontendAtsc3CodeRate.UNDEFINED; + /** + * Hardware is able to detect and set code rate automatically + */ + public static final int CODERATE_AUTO = Constants.FrontendAtsc3CodeRate.AUTO; + /** + * 2/15 code rate. + */ + public static final int CODERATE_2_15 = Constants.FrontendAtsc3CodeRate.CODERATE_2_15; + /** + * 3/15 code rate. + */ + public static final int CODERATE_3_15 = Constants.FrontendAtsc3CodeRate.CODERATE_3_15; + /** + * 4/15 code rate. + */ + public static final int CODERATE_4_15 = Constants.FrontendAtsc3CodeRate.CODERATE_4_15; + /** + * 5/15 code rate. + */ + public static final int CODERATE_5_15 = Constants.FrontendAtsc3CodeRate.CODERATE_5_15; + /** + * 6/15 code rate. + */ + public static final int CODERATE_6_15 = Constants.FrontendAtsc3CodeRate.CODERATE_6_15; + /** + * 7/15 code rate. + */ + public static final int CODERATE_7_15 = Constants.FrontendAtsc3CodeRate.CODERATE_7_15; + /** + * 8/15 code rate. + */ + public static final int CODERATE_8_15 = Constants.FrontendAtsc3CodeRate.CODERATE_8_15; + /** + * 9/15 code rate. + */ + public static final int CODERATE_9_15 = Constants.FrontendAtsc3CodeRate.CODERATE_9_15; + /** + * 10/15 code rate. + */ + public static final int CODERATE_10_15 = Constants.FrontendAtsc3CodeRate.CODERATE_10_15; + /** + * 11/15 code rate. + */ + public static final int CODERATE_11_15 = Constants.FrontendAtsc3CodeRate.CODERATE_11_15; + /** + * 12/15 code rate. + */ + public static final int CODERATE_12_15 = Constants.FrontendAtsc3CodeRate.CODERATE_12_15; + /** + * 13/15 code rate. + */ + public static final int CODERATE_13_15 = Constants.FrontendAtsc3CodeRate.CODERATE_13_15; + + + /** @hide */ + @IntDef(flag = true, + prefix = "FEC_", + value = {FEC_UNDEFINED, FEC_AUTO, FEC_BCH_LDPC_16K, FEC_BCH_LDPC_64K, FEC_CRC_LDPC_16K, + FEC_CRC_LDPC_64K, FEC_LDPC_16K, FEC_LDPC_64K}) + @Retention(RetentionPolicy.SOURCE) + public @interface Fec {} + + /** + * Forward Error Correction undefined. + */ + public static final int FEC_UNDEFINED = Constants.FrontendAtsc3Fec.UNDEFINED; + /** + * Hardware is able to detect and set FEC automatically + */ + public static final int FEC_AUTO = Constants.FrontendAtsc3Fec.AUTO; + /** + * BCH LDPC 16K Forward Error Correction + */ + public static final int FEC_BCH_LDPC_16K = Constants.FrontendAtsc3Fec.BCH_LDPC_16K; + /** + * BCH LDPC 64K Forward Error Correction + */ + public static final int FEC_BCH_LDPC_64K = Constants.FrontendAtsc3Fec.BCH_LDPC_64K; + /** + * CRC LDPC 16K Forward Error Correction + */ + public static final int FEC_CRC_LDPC_16K = Constants.FrontendAtsc3Fec.CRC_LDPC_16K; + /** + * CRC LDPC 64K Forward Error Correction + */ + public static final int FEC_CRC_LDPC_64K = Constants.FrontendAtsc3Fec.CRC_LDPC_64K; + /** + * LDPC 16K Forward Error Correction + */ + public static final int FEC_LDPC_16K = Constants.FrontendAtsc3Fec.LDPC_16K; + /** + * LDPC 64K Forward Error Correction + */ + public static final int FEC_LDPC_64K = Constants.FrontendAtsc3Fec.LDPC_64K; + + + /** @hide */ + @IntDef(flag = true, + prefix = "DEMOD_OUTPUT_FORMAT_", + value = {DEMOD_OUTPUT_FORMAT_UNDEFINED, DEMOD_OUTPUT_FORMAT_ATSC3_LINKLAYER_PACKET, + DEMOD_OUTPUT_FORMAT_BASEBAND_PACKET}) + @Retention(RetentionPolicy.SOURCE) + public @interface DemodOutputFormat {} + + /** + * Demod output format undefined. + */ + public static final int DEMOD_OUTPUT_FORMAT_UNDEFINED = + Constants.FrontendAtsc3DemodOutputFormat.UNDEFINED; + /** + * ALP format. Typically used in US region. + */ + public static final int DEMOD_OUTPUT_FORMAT_ATSC3_LINKLAYER_PACKET = + Constants.FrontendAtsc3DemodOutputFormat.ATSC3_LINKLAYER_PACKET; + /** + * BaseBand packet format. Typically used in Korea region. + */ + public static final int DEMOD_OUTPUT_FORMAT_BASEBAND_PACKET = + Constants.FrontendAtsc3DemodOutputFormat.BASEBAND_PACKET; + + public final int mBandwidth; + public final int mDemodOutputFormat; + public final Atsc3PlpSettings[] mPlpSettings; + + private Atsc3FrontendSettings(int frequency, int bandwidth, int demodOutputFormat, + Atsc3PlpSettings[] plpSettings) { super(frequency); + mBandwidth = bandwidth; + mDemodOutputFormat = demodOutputFormat; + mPlpSettings = plpSettings; + } + + /** + * Gets bandwidth. + */ + @Bandwidth + public int getBandwidth() { + return mBandwidth; + } + /** + * Gets Demod Output Format. + */ + @DemodOutputFormat + public int getDemodOutputFormat() { + return mDemodOutputFormat; + } + /** + * Gets PLP Settings. + */ + public Atsc3PlpSettings[] getPlpSettings() { + return mPlpSettings; + } + + /** + * Creates a builder for {@link Atsc3FrontendSettings}. + * + * @param context the context of the caller. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @NonNull + public static Builder builder(@NonNull Context context) { + TunerUtils.checkTunerPermission(context); + return new Builder(); + } + + /** + * Builder for {@link Atsc3FrontendSettings}. + */ + public static class Builder extends FrontendSettings.Builder<Builder> { + private int mBandwidth; + private byte mDemodOutputFormat; + private Atsc3PlpSettings[] mPlpSettings; + + private Builder() { + } + + /** + * Sets bandwidth. + */ + @NonNull + public Builder setBandwidth(int bandwidth) { + mBandwidth = bandwidth; + return this; + } + /** + * Sets Demod Output Format. + */ + @NonNull + public Builder setDemodOutputFormat(byte demodOutputFormat) { + mDemodOutputFormat = demodOutputFormat; + return this; + } + /** + * Sets PLP Settings. + */ + @NonNull + public Builder setPlpSettings(Atsc3PlpSettings[] plpSettings) { + mPlpSettings = plpSettings; + return this; + } + + /** + * Builds a {@link Atsc3FrontendSettings} object. + */ + @NonNull + public Atsc3FrontendSettings build() { + return new Atsc3FrontendSettings( + mFrequency, mBandwidth, mDemodOutputFormat, mPlpSettings); + } + + @Override + Builder self() { + return this; + } } @Override diff --git a/media/java/android/media/tv/tuner/frontend/Atsc3PlpSettings.java b/media/java/android/media/tv/tuner/frontend/Atsc3PlpSettings.java index 61c6fec154a8..43a68a088c33 100644 --- a/media/java/android/media/tv/tuner/frontend/Atsc3PlpSettings.java +++ b/media/java/android/media/tv/tuner/frontend/Atsc3PlpSettings.java @@ -16,14 +16,138 @@ package android.media.tv.tuner.frontend; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.content.Context; +import android.media.tv.tuner.TunerUtils; + /** * PLP settings for ATSC-3. * @hide */ public class Atsc3PlpSettings { - public byte plpId; - public int modulation; - public int interleaveMode; - public int codeRate; - public int fec; + private final int mPlpId; + private final int mModulation; + private final int mInterleaveMode; + private final int mCodeRate; + private final int mFec; + + private Atsc3PlpSettings(int plpId, int modulation, int interleaveMode, int codeRate, int fec) { + mPlpId = plpId; + mModulation = modulation; + mInterleaveMode = interleaveMode; + mCodeRate = codeRate; + mFec = fec; + } + + /** + * Gets Physical Layer Pipe (PLP) ID. + */ + public int getPlpId() { + return mPlpId; + } + /** + * Gets Modulation. + */ + @Atsc3FrontendSettings.Modulation + public int getModulation() { + return mModulation; + } + /** + * Gets Interleave Mode. + */ + @Atsc3FrontendSettings.TimeInterleaveMode + public int getInterleaveMode() { + return mInterleaveMode; + } + /** + * Gets Code Rate. + */ + @Atsc3FrontendSettings.CodeRate + public int getCodeRate() { + return mCodeRate; + } + /** + * Gets Forward Error Correction. + */ + @Atsc3FrontendSettings.Fec + public int getFec() { + return mFec; + } + + /** + * Creates a builder for {@link Atsc3PlpSettings}. + * + * @param context the context of the caller. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @NonNull + public static Builder builder(@NonNull Context context) { + TunerUtils.checkTunerPermission(context); + return new Builder(); + } + + /** + * Builder for {@link Atsc3PlpSettings}. + */ + public static class Builder { + private int mPlpId; + private int mModulation; + private int mInterleaveMode; + private int mCodeRate; + private int mFec; + + private Builder() { + } + + /** + * Sets Physical Layer Pipe (PLP) ID. + */ + @NonNull + public Builder setPlpId(int plpId) { + mPlpId = plpId; + return this; + } + /** + * Sets Modulation. + */ + @NonNull + public Builder setModulation(@Atsc3FrontendSettings.Modulation int modulation) { + mModulation = modulation; + return this; + } + /** + * Sets Interleave Mode. + */ + @NonNull + public Builder setInterleaveMode( + @Atsc3FrontendSettings.TimeInterleaveMode int interleaveMode) { + mInterleaveMode = interleaveMode; + return this; + } + /** + * Sets Code Rate. + */ + @NonNull + public Builder setCodeRate(@Atsc3FrontendSettings.CodeRate int codeRate) { + mCodeRate = codeRate; + return this; + } + /** + * Sets Forward Error Correction. + */ + @NonNull + public Builder setFec(@Atsc3FrontendSettings.Fec int fec) { + mFec = fec; + return this; + } + + /** + * Builds a {@link Atsc3PlpSettings} object. + */ + @NonNull + public Atsc3PlpSettings build() { + return new Atsc3PlpSettings(mPlpId, mModulation, mInterleaveMode, mCodeRate, mFec); + } + } } diff --git a/media/java/android/media/tv/tuner/frontend/AtscFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/AtscFrontendCapabilities.java index 6ae3c632f5db..0ff516de3603 100644 --- a/media/java/android/media/tv/tuner/frontend/AtscFrontendCapabilities.java +++ b/media/java/android/media/tv/tuner/frontend/AtscFrontendCapabilities.java @@ -23,10 +23,14 @@ package android.media.tv.tuner.frontend; public class AtscFrontendCapabilities extends FrontendCapabilities { private final int mModulationCap; - AtscFrontendCapabilities(int modulationCap) { + private AtscFrontendCapabilities(int modulationCap) { mModulationCap = modulationCap; } - /** Gets modulation capability. */ + + /** + * Gets modulation capability. + */ + @AtscFrontendSettings.Modulation public int getModulationCapability() { return mModulationCap; } diff --git a/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java index 19e18d017e67..32901d8aadec 100644 --- a/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java +++ b/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java @@ -16,15 +16,105 @@ package android.media.tv.tuner.frontend; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.content.Context; +import android.hardware.tv.tuner.V1_0.Constants; +import android.media.tv.tuner.TunerUtils; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Frontend settings for ATSC. * @hide */ public class AtscFrontendSettings extends FrontendSettings { - public int modulation; - AtscFrontendSettings(int frequency) { + /** @hide */ + @IntDef(flag = true, + prefix = "MODULATION_", + value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_8VSB, + MODULATION_MOD_16VSB}) + @Retention(RetentionPolicy.SOURCE) + public @interface Modulation {} + + /** + * Modulation undefined. + */ + public static final int MODULATION_UNDEFINED = Constants.FrontendAtscModulation.UNDEFINED; + /** + * Hardware is able to detect and set modulation automatically + */ + public static final int MODULATION_AUTO = Constants.FrontendAtscModulation.AUTO; + /** + * 8VSB Modulation. + */ + public static final int MODULATION_MOD_8VSB = Constants.FrontendAtscModulation.MOD_8VSB; + /** + * 16VSB Modulation. + */ + public static final int MODULATION_MOD_16VSB = Constants.FrontendAtscModulation.MOD_16VSB; + + + private final int mModulation; + + private AtscFrontendSettings(int frequency, int modulation) { super(frequency); + mModulation = modulation; + } + + /** + * Gets Modulation. + */ + @Modulation + public int getModulation() { + return mModulation; + } + + /** + * Creates a builder for {@link AtscFrontendSettings}. + * + * @param context the context of the caller. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @NonNull + public static Builder builder(@NonNull Context context) { + TunerUtils.checkTunerPermission(context); + return new Builder(); + } + + /** + * Builder for {@link AtscFrontendSettings}. + */ + public static class Builder extends FrontendSettings.Builder<Builder> { + private int mModulation; + + private Builder() { + } + + /** + * Sets Modulation. + */ + @NonNull + public Builder setModulation(@Modulation int modulation) { + mModulation = modulation; + return this; + } + + /** + * Builds a {@link AtscFrontendSettings} object. + */ + @NonNull + public AtscFrontendSettings build() { + return new AtscFrontendSettings(mFrequency, mModulation); + } + + @Override + Builder self() { + return this; + } } @Override diff --git a/media/java/android/media/tv/tuner/frontend/DvbcFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/DvbcFrontendCapabilities.java index edea7af06774..f3fbdb5f3b18 100644 --- a/media/java/android/media/tv/tuner/frontend/DvbcFrontendCapabilities.java +++ b/media/java/android/media/tv/tuner/frontend/DvbcFrontendCapabilities.java @@ -16,6 +16,8 @@ package android.media.tv.tuner.frontend; +import android.media.tv.tuner.TunerConstants.FrontendInnerFec; + /** * DVBC Capabilities. * @hide @@ -25,21 +27,30 @@ public class DvbcFrontendCapabilities extends FrontendCapabilities { private final int mFecCap; private final int mAnnexCap; - DvbcFrontendCapabilities(int modulationCap, int fecCap, int annexCap) { + private DvbcFrontendCapabilities(int modulationCap, int fecCap, int annexCap) { mModulationCap = modulationCap; mFecCap = fecCap; mAnnexCap = annexCap; } - /** Gets modulation capability. */ + /** + * Gets modulation capability. + */ + @DvbcFrontendSettings.Modulation public int getModulationCapability() { return mModulationCap; } - /** Gets FEC capability. */ + /** + * Gets inner FEC capability. + */ + @FrontendInnerFec public int getFecCapability() { return mFecCap; } - /** Gets annex capability. */ + /** + * Gets annex capability. + */ + @DvbcFrontendSettings.Annex public int getAnnexCapability() { return mAnnexCap; } diff --git a/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java index 60618f6f6896..3d212d3d55c3 100644 --- a/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java +++ b/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java @@ -16,20 +16,279 @@ package android.media.tv.tuner.frontend; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.content.Context; +import android.hardware.tv.tuner.V1_0.Constants; +import android.media.tv.tuner.TunerConstants.FrontendInnerFec; +import android.media.tv.tuner.TunerUtils; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Frontend settings for DVBC. * @hide */ public class DvbcFrontendSettings extends FrontendSettings { - public int modulation; - public long fec; - public int symbolRate; - public int outerFec; - public byte annex; - public int spectralInversion; - - DvbcFrontendSettings(int frequency) { + + /** @hide */ + @IntDef(flag = true, + prefix = "MODULATION_", + value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_16QAM, + MODULATION_MOD_32QAM, MODULATION_MOD_64QAM, MODULATION_MOD_128QAM, + MODULATION_MOD_256QAM}) + @Retention(RetentionPolicy.SOURCE) + public @interface Modulation {} + + /** + * Modulation undefined. + */ + public static final int MODULATION_UNDEFINED = Constants.FrontendDvbcModulation.UNDEFINED; + /** + * Hardware is able to detect and set modulation automatically + */ + public static final int MODULATION_AUTO = Constants.FrontendDvbcModulation.AUTO; + /** + * 16QAM Modulation. + */ + public static final int MODULATION_MOD_16QAM = Constants.FrontendDvbcModulation.MOD_16QAM; + /** + * 32QAM Modulation. + */ + public static final int MODULATION_MOD_32QAM = Constants.FrontendDvbcModulation.MOD_32QAM; + /** + * 64QAM Modulation. + */ + public static final int MODULATION_MOD_64QAM = Constants.FrontendDvbcModulation.MOD_64QAM; + /** + * 128QAM Modulation. + */ + public static final int MODULATION_MOD_128QAM = Constants.FrontendDvbcModulation.MOD_128QAM; + /** + * 256QAM Modulation. + */ + public static final int MODULATION_MOD_256QAM = Constants.FrontendDvbcModulation.MOD_256QAM; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "OUTER_FEC_", + value = {OUTER_FEC_UNDEFINED, OUTER_FEC_OUTER_FEC_NONE, OUTER_FEC_OUTER_FEC_RS}) + public @interface OuterFec {} + + /** + * Outer Forward Error Correction (FEC) Type undefined. + */ + public static final int OUTER_FEC_UNDEFINED = Constants.FrontendDvbcOuterFec.UNDEFINED; + /** + * None Outer Forward Error Correction (FEC) Type. + */ + public static final int OUTER_FEC_OUTER_FEC_NONE = + Constants.FrontendDvbcOuterFec.OUTER_FEC_NONE; + /** + * RS Outer Forward Error Correction (FEC) Type. + */ + public static final int OUTER_FEC_OUTER_FEC_RS = Constants.FrontendDvbcOuterFec.OUTER_FEC_RS; + + + /** @hide */ + @IntDef(flag = true, + prefix = "ANNEX_", + value = {ANNEX_UNDEFINED, ANNEX_A, ANNEX_B, ANNEX_C}) + @Retention(RetentionPolicy.SOURCE) + public @interface Annex {} + + /** + * Annex Type undefined. + */ + public static final int ANNEX_UNDEFINED = Constants.FrontendDvbcAnnex.UNDEFINED; + /** + * Annex Type A. + */ + public static final int ANNEX_A = Constants.FrontendDvbcAnnex.A; + /** + * Annex Type B. + */ + public static final int ANNEX_B = Constants.FrontendDvbcAnnex.B; + /** + * Annex Type C. + */ + public static final int ANNEX_C = Constants.FrontendDvbcAnnex.C; + + + /** @hide */ + @IntDef(prefix = "SPECTRAL_INVERSION_", + value = {SPECTRAL_INVERSION_UNDEFINED, SPECTRAL_INVERSION_NORMAL, + SPECTRAL_INVERSION_INVERTED}) + @Retention(RetentionPolicy.SOURCE) + public @interface SpectralInversion {} + + /** + * Spectral Inversion Type undefined. + */ + public static final int SPECTRAL_INVERSION_UNDEFINED = + Constants.FrontendDvbcSpectralInversion.UNDEFINED; + /** + * Normal Spectral Inversion. + */ + public static final int SPECTRAL_INVERSION_NORMAL = + Constants.FrontendDvbcSpectralInversion.NORMAL; + /** + * Inverted Spectral Inversion. + */ + public static final int SPECTRAL_INVERSION_INVERTED = + Constants.FrontendDvbcSpectralInversion.INVERTED; + + + private final int mModulation; + private final long mFec; + private final int mSymbolRate; + private final int mOuterFec; + private final byte mAnnex; + private final int mSpectralInversion; + + private DvbcFrontendSettings(int frequency, int modulation, long fec, int symbolRate, + int outerFec, byte annex, int spectralInversion) { super(frequency); + mModulation = modulation; + mFec = fec; + mSymbolRate = symbolRate; + mOuterFec = outerFec; + mAnnex = annex; + mSpectralInversion = spectralInversion; + } + + /** + * Gets Modulation. + */ + @Modulation + public int getModulation() { + return mModulation; + } + /** + * Gets Inner Forward Error Correction. + */ + @FrontendInnerFec + public long getFec() { + return mFec; + } + /** + * Gets Symbol Rate in symbols per second. + */ + public int getSymbolRate() { + return mSymbolRate; + } + /** + * Gets Outer Forward Error Correction. + */ + @OuterFec + public int getOuterFec() { + return mOuterFec; + } + /** + * Gets Annex. + */ + @Annex + public byte getAnnex() { + return mAnnex; + } + /** + * Gets Spectral Inversion. + */ + @SpectralInversion + public int getSpectralInversion() { + return mSpectralInversion; + } + + /** + * Creates a builder for {@link DvbcFrontendSettings}. + * + * @param context the context of the caller. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @NonNull + public static Builder builder(@NonNull Context context) { + TunerUtils.checkTunerPermission(context); + return new Builder(); + } + + /** + * Builder for {@link DvbcFrontendSettings}. + */ + public static class Builder extends FrontendSettings.Builder<Builder> { + private int mModulation; + private long mFec; + private int mSymbolRate; + private int mOuterFec; + private byte mAnnex; + private int mSpectralInversion; + + private Builder() { + } + + /** + * Sets Modulation. + */ + @NonNull + public Builder setModulation(@Modulation int modulation) { + mModulation = modulation; + return this; + } + /** + * Sets Inner Forward Error Correction. + */ + @NonNull + public Builder setFec(@FrontendInnerFec long fec) { + mFec = fec; + return this; + } + /** + * Sets Symbol Rate in symbols per second. + */ + @NonNull + public Builder setSymbolRate(int symbolRate) { + mSymbolRate = symbolRate; + return this; + } + /** + * Sets Outer Forward Error Correction. + */ + @NonNull + public Builder setOuterFec(@OuterFec int outerFec) { + mOuterFec = outerFec; + return this; + } + /** + * Sets Annex. + */ + @NonNull + public Builder setAnnex(@Annex byte annex) { + mAnnex = annex; + return this; + } + /** + * Sets Spectral Inversion. + */ + @NonNull + public Builder setSpectralInversion(@SpectralInversion int spectralInversion) { + mSpectralInversion = spectralInversion; + return this; + } + + /** + * Builds a {@link DvbcFrontendSettings} object. + */ + @NonNull + public DvbcFrontendSettings build() { + return new DvbcFrontendSettings(mFrequency, mModulation, mFec, mSymbolRate, mOuterFec, + mAnnex, mSpectralInversion); + } + + @Override + Builder self() { + return this; + } } @Override diff --git a/media/java/android/media/tv/tuner/frontend/DvbsCodeRate.java b/media/java/android/media/tv/tuner/frontend/DvbsCodeRate.java index bfa439154172..04d33750849c 100644 --- a/media/java/android/media/tv/tuner/frontend/DvbsCodeRate.java +++ b/media/java/android/media/tv/tuner/frontend/DvbsCodeRate.java @@ -16,13 +16,118 @@ package android.media.tv.tuner.frontend; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.content.Context; +import android.media.tv.tuner.TunerConstants.FrontendInnerFec; +import android.media.tv.tuner.TunerUtils; + /** * Code rate for DVBS. * @hide */ public class DvbsCodeRate { - public long fec; - public boolean isLinear; - public boolean isShortFrames; - public int bitsPer1000Symbol; + private final long mFec; + private final boolean mIsLinear; + private final boolean mIsShortFrames; + private final int mBitsPer1000Symbol; + + private DvbsCodeRate(long fec, boolean isLinear, boolean isShortFrames, int bitsPer1000Symbol) { + mFec = fec; + mIsLinear = isLinear; + mIsShortFrames = isShortFrames; + mBitsPer1000Symbol = bitsPer1000Symbol; + } + + /** + * Gets inner FEC. + */ + @FrontendInnerFec + public long getFec() { + return mFec; + } + /** + * Checks whether it's linear. + */ + public boolean isLinear() { + return mIsLinear; + } + /** + * Checks whether short frame enabled. + */ + public boolean isShortFrameEnabled() { + return mIsShortFrames; + } + /** + * Gets bits number in 1000 symbols. 0 by default. + */ + public int getBitsPer1000Symbol() { + return mBitsPer1000Symbol; + } + + /** + * Creates a builder for {@link DvbsCodeRate}. + * + * @param context the context of the caller. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @NonNull + public static Builder builder(@NonNull Context context) { + TunerUtils.checkTunerPermission(context); + return new Builder(); + } + + /** + * Builder for {@link DvbsCodeRate}. + */ + public static class Builder { + private long mFec; + private boolean mIsLinear; + private boolean mIsShortFrames; + private int mBitsPer1000Symbol; + + private Builder() { + } + + /** + * Sets inner FEC. + */ + @NonNull + public Builder setFec(@FrontendInnerFec long fec) { + mFec = fec; + return this; + } + /** + * Sets whether it's linear. + */ + @NonNull + public Builder setLinear(boolean isLinear) { + mIsLinear = isLinear; + return this; + } + /** + * Sets whether short frame enabled. + */ + @NonNull + public Builder setShortFrameEnabled(boolean isShortFrames) { + mIsShortFrames = isShortFrames; + return this; + } + /** + * Sets bits number in 1000 symbols. + */ + @NonNull + public Builder setBitsPer1000Symbol(int bitsPer1000Symbol) { + mBitsPer1000Symbol = bitsPer1000Symbol; + return this; + } + + /** + * Builds a {@link DvbsCodeRate} object. + */ + @NonNull + public DvbsCodeRate build() { + return new DvbsCodeRate(mFec, mIsLinear, mIsShortFrames, mBitsPer1000Symbol); + } + } } diff --git a/media/java/android/media/tv/tuner/frontend/DvbsFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/DvbsFrontendCapabilities.java index f5a41574cd04..bd615d033bc6 100644 --- a/media/java/android/media/tv/tuner/frontend/DvbsFrontendCapabilities.java +++ b/media/java/android/media/tv/tuner/frontend/DvbsFrontendCapabilities.java @@ -16,6 +16,8 @@ package android.media.tv.tuner.frontend; +import android.media.tv.tuner.TunerConstants.FrontendInnerFec; + /** * DVBS Capabilities. * @hide @@ -25,21 +27,30 @@ public class DvbsFrontendCapabilities extends FrontendCapabilities { private final long mInnerFecCap; private final int mStandard; - DvbsFrontendCapabilities(int modulationCap, long innerFecCap, int standard) { + private DvbsFrontendCapabilities(int modulationCap, long innerFecCap, int standard) { mModulationCap = modulationCap; mInnerFecCap = innerFecCap; mStandard = standard; } - /** Gets modulation capability. */ + /** + * Gets modulation capability. + */ + @DvbsFrontendSettings.Modulation public int getModulationCapability() { return mModulationCap; } - /** Gets inner FEC capability. */ + /** + * Gets inner FEC capability. + */ + @FrontendInnerFec public long getInnerFecCapability() { return mInnerFecCap; } - /** Gets DVBS standard capability. */ + /** + * Gets DVBS standard capability. + */ + @DvbsFrontendSettings.Standard public int getStandardCapability() { return mStandard; } diff --git a/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java index 586787f9eb73..5b3bffc44741 100644 --- a/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java +++ b/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java @@ -16,21 +16,344 @@ package android.media.tv.tuner.frontend; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.content.Context; +import android.hardware.tv.tuner.V1_0.Constants; +import android.media.tv.tuner.TunerUtils; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Frontend settings for DVBS. * @hide */ public class DvbsFrontendSettings extends FrontendSettings { - public int modulation; - public DvbsCodeRate coderate; - public int symbolRate; - public int rolloff; - public int pilot; - public int inputStreamId; - public byte standard; - - DvbsFrontendSettings(int frequency) { + /** @hide */ + @IntDef(flag = true, + prefix = "MODULATION_", + value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_QPSK, + MODULATION_MOD_8PSK, MODULATION_MOD_16QAM, MODULATION_MOD_16PSK, + MODULATION_MOD_32PSK, MODULATION_MOD_ACM, MODULATION_MOD_8APSK, + MODULATION_MOD_16APSK, MODULATION_MOD_32APSK, MODULATION_MOD_64APSK, + MODULATION_MOD_128APSK, MODULATION_MOD_256APSK, MODULATION_MOD_RESERVED}) + @Retention(RetentionPolicy.SOURCE) + public @interface Modulation {} + + /** + * Modulation undefined. + */ + public static final int MODULATION_UNDEFINED = Constants.FrontendDvbsModulation.UNDEFINED; + /** + * Hardware is able to detect and set modulation automatically + */ + public static final int MODULATION_AUTO = Constants.FrontendDvbsModulation.AUTO; + /** + * QPSK Modulation. + */ + public static final int MODULATION_MOD_QPSK = Constants.FrontendDvbsModulation.MOD_QPSK; + /** + * 8PSK Modulation. + */ + public static final int MODULATION_MOD_8PSK = Constants.FrontendDvbsModulation.MOD_8PSK; + /** + * 16QAM Modulation. + */ + public static final int MODULATION_MOD_16QAM = Constants.FrontendDvbsModulation.MOD_16QAM; + /** + * 16PSK Modulation. + */ + public static final int MODULATION_MOD_16PSK = Constants.FrontendDvbsModulation.MOD_16PSK; + /** + * 32PSK Modulation. + */ + public static final int MODULATION_MOD_32PSK = Constants.FrontendDvbsModulation.MOD_32PSK; + /** + * ACM Modulation. + */ + public static final int MODULATION_MOD_ACM = Constants.FrontendDvbsModulation.MOD_ACM; + /** + * 8APSK Modulation. + */ + public static final int MODULATION_MOD_8APSK = Constants.FrontendDvbsModulation.MOD_8APSK; + /** + * 16APSK Modulation. + */ + public static final int MODULATION_MOD_16APSK = Constants.FrontendDvbsModulation.MOD_16APSK; + /** + * 32APSK Modulation. + */ + public static final int MODULATION_MOD_32APSK = Constants.FrontendDvbsModulation.MOD_32APSK; + /** + * 64APSK Modulation. + */ + public static final int MODULATION_MOD_64APSK = Constants.FrontendDvbsModulation.MOD_64APSK; + /** + * 128APSK Modulation. + */ + public static final int MODULATION_MOD_128APSK = Constants.FrontendDvbsModulation.MOD_128APSK; + /** + * 256APSK Modulation. + */ + public static final int MODULATION_MOD_256APSK = Constants.FrontendDvbsModulation.MOD_256APSK; + /** + * Reversed Modulation. + */ + public static final int MODULATION_MOD_RESERVED = Constants.FrontendDvbsModulation.MOD_RESERVED; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "ROLLOFF_", + value = {ROLLOFF_UNDEFINED, ROLLOFF_0_35, ROLLOFF_0_25, ROLLOFF_0_20, ROLLOFF_0_15, + ROLLOFF_0_10, ROLLOFF_0_5}) + public @interface Rolloff {} + + /** + * Roll Off undefined. + */ + public static final int ROLLOFF_UNDEFINED = Constants.FrontendDvbsRolloff.UNDEFINED; + /** + * Roll Off 0_35. + */ + public static final int ROLLOFF_0_35 = Constants.FrontendDvbsRolloff.ROLLOFF_0_35; + /** + * Roll Off 0_25. + */ + public static final int ROLLOFF_0_25 = Constants.FrontendDvbsRolloff.ROLLOFF_0_25; + /** + * Roll Off 0_2. + */ + public static final int ROLLOFF_0_20 = Constants.FrontendDvbsRolloff.ROLLOFF_0_20; + /** + * Roll Off 0_15. + */ + public static final int ROLLOFF_0_15 = Constants.FrontendDvbsRolloff.ROLLOFF_0_15; + /** + * Roll Off 0_1. + */ + public static final int ROLLOFF_0_10 = Constants.FrontendDvbsRolloff.ROLLOFF_0_10; + /** + * Roll Off 0_5. + */ + public static final int ROLLOFF_0_5 = Constants.FrontendDvbsRolloff.ROLLOFF_0_5; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "PILOT_", + value = {PILOT_UNDEFINED, PILOT_ON, PILOT_OFF, PILOT_AUTO}) + public @interface Pilot {} + + /** + * Pilot mode undefined. + */ + public static final int PILOT_UNDEFINED = Constants.FrontendDvbsPilot.UNDEFINED; + /** + * Pilot mode on. + */ + public static final int PILOT_ON = Constants.FrontendDvbsPilot.ON; + /** + * Pilot mode off. + */ + public static final int PILOT_OFF = Constants.FrontendDvbsPilot.OFF; + /** + * Pilot mode auto. + */ + public static final int PILOT_AUTO = Constants.FrontendDvbsPilot.AUTO; + + + /** @hide */ + @IntDef(flag = true, + prefix = "STANDARD_", + value = {STANDARD_AUTO, STANDARD_S, STANDARD_S2, STANDARD_S2X}) + @Retention(RetentionPolicy.SOURCE) + public @interface Standard {} + + /** + * Standard undefined. + */ + public static final int STANDARD_AUTO = Constants.FrontendDvbsStandard.AUTO; + /** + * Standard S. + */ + public static final int STANDARD_S = Constants.FrontendDvbsStandard.S; + /** + * Standard S2. + */ + public static final int STANDARD_S2 = Constants.FrontendDvbsStandard.S2; + /** + * Standard S2X. + */ + public static final int STANDARD_S2X = Constants.FrontendDvbsStandard.S2X; + + + private final int mModulation; + private final DvbsCodeRate mCoderate; + private final int mSymbolRate; + private final int mRolloff; + private final int mPilot; + private final int mInputStreamId; + private final int mStandard; + + private DvbsFrontendSettings(int frequency, int modulation, DvbsCodeRate coderate, + int symbolRate, int rolloff, int pilot, int inputStreamId, int standard) { super(frequency); + mModulation = modulation; + mCoderate = coderate; + mSymbolRate = symbolRate; + mRolloff = rolloff; + mPilot = pilot; + mInputStreamId = inputStreamId; + mStandard = standard; + } + + /** + * Gets Modulation. + */ + @Modulation + public int getModulation() { + return mModulation; + } + /** + * Gets Code rate. + */ + @Nullable + public DvbsCodeRate getCoderate() { + return mCoderate; + } + /** + * Gets Symbol Rate in symbols per second. + */ + public int getSymbolRate() { + return mSymbolRate; + } + /** + * Gets Rolloff. + */ + @Rolloff + public int getRolloff() { + return mRolloff; + } + /** + * Gets Pilot mode. + */ + @Pilot + public int getPilot() { + return mPilot; + } + /** + * Gets Input Stream ID. + */ + public int getInputStreamId() { + return mInputStreamId; + } + /** + * Gets DVBS sub-standard. + */ + @Standard + public int getStandard() { + return mStandard; + } + + /** + * Creates a builder for {@link DvbsFrontendSettings}. + * + * @param context the context of the caller. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @NonNull + public static Builder builder(@NonNull Context context) { + TunerUtils.checkTunerPermission(context); + return new Builder(); + } + + /** + * Builder for {@link DvbsFrontendSettings}. + */ + public static class Builder extends FrontendSettings.Builder<Builder> { + private int mModulation; + private DvbsCodeRate mCoderate; + private int mSymbolRate; + private int mRolloff; + private int mPilot; + private int mInputStreamId; + private int mStandard; + + private Builder() { + } + + /** + * Sets Modulation. + */ + @NonNull + public Builder setModulation(@Modulation int modulation) { + mModulation = modulation; + return this; + } + /** + * Sets Code rate. + */ + @NonNull + public Builder setCoderate(@Nullable DvbsCodeRate coderate) { + mCoderate = coderate; + return this; + } + /** + * Sets Symbol Rate. + */ + @NonNull + public Builder setSymbolRate(int symbolRate) { + mSymbolRate = symbolRate; + return this; + } + /** + * Sets Rolloff. + */ + @NonNull + public Builder setRolloff(@Rolloff int rolloff) { + mRolloff = rolloff; + return this; + } + /** + * Sets Pilot mode. + */ + @NonNull + public Builder setPilot(@Pilot int pilot) { + mPilot = pilot; + return this; + } + /** + * Sets Input Stream ID. + */ + @NonNull + public Builder setInputStreamId(int inputStreamId) { + mInputStreamId = inputStreamId; + return this; + } + /** + * Sets Standard. + */ + @NonNull + public Builder setStandard(@Standard int standard) { + mStandard = standard; + return this; + } + + /** + * Builds a {@link DvbsFrontendSettings} object. + */ + @NonNull + public DvbsFrontendSettings build() { + return new DvbsFrontendSettings(mFrequency, mModulation, mCoderate, mSymbolRate, + mRolloff, mPilot, mInputStreamId, mStandard); + } + + @Override + Builder self() { + return this; + } } @Override diff --git a/media/java/android/media/tv/tuner/frontend/DvbtFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/DvbtFrontendCapabilities.java index e9c16ddd4dc8..0d4717903f19 100644 --- a/media/java/android/media/tv/tuner/frontend/DvbtFrontendCapabilities.java +++ b/media/java/android/media/tv/tuner/frontend/DvbtFrontendCapabilities.java @@ -30,9 +30,9 @@ public class DvbtFrontendCapabilities extends FrontendCapabilities { private final boolean mIsT2Supported; private final boolean mIsMisoSupported; - DvbtFrontendCapabilities(int transmissionModeCap, int bandwidthCap, int constellationCap, - int coderateCap, int hierarchyCap, int guardIntervalCap, boolean isT2Supported, - boolean isMisoSupported) { + private DvbtFrontendCapabilities(int transmissionModeCap, int bandwidthCap, + int constellationCap, int coderateCap, int hierarchyCap, int guardIntervalCap, + boolean isT2Supported, boolean isMisoSupported) { mTransmissionModeCap = transmissionModeCap; mBandwidthCap = bandwidthCap; mConstellationCap = constellationCap; @@ -43,36 +43,58 @@ public class DvbtFrontendCapabilities extends FrontendCapabilities { mIsMisoSupported = isMisoSupported; } - /** Gets transmission mode capability. */ + /** + * Gets transmission mode capability. + */ + @DvbtFrontendSettings.TransmissionMode public int getTransmissionModeCapability() { return mTransmissionModeCap; } - /** Gets bandwidth capability. */ + /** + * Gets bandwidth capability. + */ + @DvbtFrontendSettings.Bandwidth public int getBandwidthCapability() { return mBandwidthCap; } - /** Gets constellation capability. */ + /** + * Gets constellation capability. + */ + @DvbtFrontendSettings.Constellation public int getConstellationCapability() { return mConstellationCap; } - /** Gets code rate capability. */ + /** + * Gets code rate capability. + */ + @DvbtFrontendSettings.Coderate public int getCodeRateCapability() { return mCoderateCap; } - /** Gets hierarchy capability. */ + /** + * Gets hierarchy capability. + */ + @DvbtFrontendSettings.Hierarchy public int getHierarchyCapability() { return mHierarchyCap; } - /** Gets guard interval capability. */ + /** + * Gets guard interval capability. + */ + @DvbtFrontendSettings.GuardInterval public int getGuardIntervalCapability() { return mGuardIntervalCap; } - /** Returns whether T2 is supported. */ - public boolean getIsT2Supported() { + /** + * Returns whether T2 is supported. + */ + public boolean isT2Supported() { return mIsT2Supported; } - /** Returns whether MISO is supported. */ - public boolean getIsMisoSupported() { + /** + * Returns whether MISO is supported. + */ + public boolean isMisoSupported() { return mIsMisoSupported; } } diff --git a/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java index 6b350a7865a2..f0469b71f32c 100644 --- a/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java +++ b/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java @@ -16,27 +16,631 @@ package android.media.tv.tuner.frontend; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.content.Context; +import android.hardware.tv.tuner.V1_0.Constants; +import android.media.tv.tuner.TunerUtils; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Frontend settings for DVBT. * @hide */ public class DvbtFrontendSettings extends FrontendSettings { - public int transmissionMode; - public int bandwidth; - public int constellation; - public int hierarchy; - public int hpCoderate; - public int lpCoderate; - public int guardInterval; - public boolean isHighPriority; - public byte standard; - public boolean isMiso; - public int plpMode; - public byte plpId; - public byte plpGroupId; - - DvbtFrontendSettings(int frequency) { + + /** @hide */ + @IntDef(flag = true, + prefix = "TRANSMISSION_MODE_", + value = {TRANSMISSION_MODE_UNDEFINED, TRANSMISSION_MODE_AUTO, + TRANSMISSION_MODE_2K, TRANSMISSION_MODE_8K, TRANSMISSION_MODE_4K, + TRANSMISSION_MODE_1K, TRANSMISSION_MODE_16K, TRANSMISSION_MODE_32K}) + @Retention(RetentionPolicy.SOURCE) + public @interface TransmissionMode {} + + /** + * Transmission Mode undefined. + */ + public static final int TRANSMISSION_MODE_UNDEFINED = + Constants.FrontendDvbtTransmissionMode.UNDEFINED; + /** + * Hardware is able to detect and set Transmission Mode automatically + */ + public static final int TRANSMISSION_MODE_AUTO = Constants.FrontendDvbtTransmissionMode.AUTO; + /** + * 2K Transmission Mode. + */ + public static final int TRANSMISSION_MODE_2K = Constants.FrontendDvbtTransmissionMode.MODE_2K; + /** + * 8K Transmission Mode. + */ + public static final int TRANSMISSION_MODE_8K = Constants.FrontendDvbtTransmissionMode.MODE_8K; + /** + * 4K Transmission Mode. + */ + public static final int TRANSMISSION_MODE_4K = Constants.FrontendDvbtTransmissionMode.MODE_4K; + /** + * 1K Transmission Mode. + */ + public static final int TRANSMISSION_MODE_1K = Constants.FrontendDvbtTransmissionMode.MODE_1K; + /** + * 16K Transmission Mode. + */ + public static final int TRANSMISSION_MODE_16K = Constants.FrontendDvbtTransmissionMode.MODE_16K; + /** + * 32K Transmission Mode. + */ + public static final int TRANSMISSION_MODE_32K = Constants.FrontendDvbtTransmissionMode.MODE_32K; + + + + /** @hide */ + @IntDef(flag = true, + prefix = "BANDWIDTH_", + value = {BANDWIDTH_UNDEFINED, BANDWIDTH_AUTO, BANDWIDTH_8MHZ, BANDWIDTH_7MHZ, + BANDWIDTH_6MHZ, BANDWIDTH_5MHZ, BANDWIDTH_1_7MHZ, BANDWIDTH_10MHZ}) + @Retention(RetentionPolicy.SOURCE) + public @interface Bandwidth {} + + /** + * Bandwidth undefined. + */ + public static final int BANDWIDTH_UNDEFINED = Constants.FrontendDvbtBandwidth.UNDEFINED; + /** + * Hardware is able to detect and set Bandwidth automatically. + */ + public static final int BANDWIDTH_AUTO = Constants.FrontendDvbtBandwidth.AUTO; + /** + * 8 MHz bandwidth. + */ + public static final int BANDWIDTH_8MHZ = Constants.FrontendDvbtBandwidth.BANDWIDTH_8MHZ; + /** + * 7 MHz bandwidth. + */ + public static final int BANDWIDTH_7MHZ = Constants.FrontendDvbtBandwidth.BANDWIDTH_7MHZ; + /** + * 6 MHz bandwidth. + */ + public static final int BANDWIDTH_6MHZ = Constants.FrontendDvbtBandwidth.BANDWIDTH_6MHZ; + /** + * 5 MHz bandwidth. + */ + public static final int BANDWIDTH_5MHZ = Constants.FrontendDvbtBandwidth.BANDWIDTH_5MHZ; + /** + * 1.7 MHz bandwidth. + */ + public static final int BANDWIDTH_1_7MHZ = Constants.FrontendDvbtBandwidth.BANDWIDTH_1_7MHZ; + /** + * 10 MHz bandwidth. + */ + public static final int BANDWIDTH_10MHZ = Constants.FrontendDvbtBandwidth.BANDWIDTH_10MHZ; + + + /** @hide */ + @IntDef(flag = true, + prefix = "CONSTELLATION_", + value = {CONSTELLATION_UNDEFINED, CONSTELLATION_AUTO, CONSTELLATION_CONSTELLATION_QPSK, + CONSTELLATION_CONSTELLATION_16QAM, CONSTELLATION_CONSTELLATION_64QAM, + CONSTELLATION_CONSTELLATION_256QAM}) + @Retention(RetentionPolicy.SOURCE) + public @interface Constellation {} + + /** + * Constellation not defined. + */ + public static final int CONSTELLATION_UNDEFINED = Constants.FrontendDvbtConstellation.UNDEFINED; + /** + * Hardware is able to detect and set Constellation automatically. + */ + public static final int CONSTELLATION_AUTO = Constants.FrontendDvbtConstellation.AUTO; + /** + * QPSK Constellation. + */ + public static final int CONSTELLATION_CONSTELLATION_QPSK = + Constants.FrontendDvbtConstellation.CONSTELLATION_QPSK; + /** + * 16QAM Constellation. + */ + public static final int CONSTELLATION_CONSTELLATION_16QAM = + Constants.FrontendDvbtConstellation.CONSTELLATION_16QAM; + /** + * 64QAM Constellation. + */ + public static final int CONSTELLATION_CONSTELLATION_64QAM = + Constants.FrontendDvbtConstellation.CONSTELLATION_64QAM; + /** + * 256QAM Constellation. + */ + public static final int CONSTELLATION_CONSTELLATION_256QAM = + Constants.FrontendDvbtConstellation.CONSTELLATION_256QAM; + + + /** @hide */ + @IntDef(flag = true, + prefix = "HIERARCHY_", + value = {HIERARCHY_UNDEFINED, HIERARCHY_AUTO, HIERARCHY_NON_NATIVE, HIERARCHY_1_NATIVE, + HIERARCHY_2_NATIVE, HIERARCHY_4_NATIVE, HIERARCHY_NON_INDEPTH, HIERARCHY_1_INDEPTH, + HIERARCHY_2_INDEPTH, HIERARCHY_4_INDEPTH}) + @Retention(RetentionPolicy.SOURCE) + public @interface Hierarchy {} + + /** + * Hierarchy undefined. + */ + public static final int HIERARCHY_UNDEFINED = Constants.FrontendDvbtHierarchy.UNDEFINED; + /** + * Hardware is able to detect and set Hierarchy automatically. + */ + public static final int HIERARCHY_AUTO = Constants.FrontendDvbtHierarchy.AUTO; + /** + * Non-native Hierarchy + */ + public static final int HIERARCHY_NON_NATIVE = + Constants.FrontendDvbtHierarchy.HIERARCHY_NON_NATIVE; + /** + * 1-native Hierarchy + */ + public static final int HIERARCHY_1_NATIVE = Constants.FrontendDvbtHierarchy.HIERARCHY_1_NATIVE; + /** + * 2-native Hierarchy + */ + public static final int HIERARCHY_2_NATIVE = Constants.FrontendDvbtHierarchy.HIERARCHY_2_NATIVE; + /** + * 4-native Hierarchy + */ + public static final int HIERARCHY_4_NATIVE = Constants.FrontendDvbtHierarchy.HIERARCHY_4_NATIVE; + /** + * Non-indepth Hierarchy + */ + public static final int HIERARCHY_NON_INDEPTH = + Constants.FrontendDvbtHierarchy.HIERARCHY_NON_INDEPTH; + /** + * 1-indepth Hierarchy + */ + public static final int HIERARCHY_1_INDEPTH = + Constants.FrontendDvbtHierarchy.HIERARCHY_1_INDEPTH; + /** + * 2-indepth Hierarchy + */ + public static final int HIERARCHY_2_INDEPTH = + Constants.FrontendDvbtHierarchy.HIERARCHY_2_INDEPTH; + /** + * 4-indepth Hierarchy + */ + public static final int HIERARCHY_4_INDEPTH = + Constants.FrontendDvbtHierarchy.HIERARCHY_4_INDEPTH; + + + /** @hide */ + @IntDef(flag = true, + prefix = "CODERATE_", + value = {CODERATE_UNDEFINED, CODERATE_AUTO, CODERATE_1_2, CODERATE_2_3, CODERATE_3_4, + CODERATE_5_6, CODERATE_7_8, CODERATE_3_5, CODERATE_4_5, CODERATE_6_7, CODERATE_8_9}) + @Retention(RetentionPolicy.SOURCE) + public @interface Coderate {} + + /** + * Code rate undefined. + */ + public static final int CODERATE_UNDEFINED = + Constants.FrontendDvbtCoderate.UNDEFINED; + /** + * Hardware is able to detect and set code rate automatically. + */ + public static final int CODERATE_AUTO = Constants.FrontendDvbtCoderate.AUTO; + /** + * 1_2 code rate. + */ + public static final int CODERATE_1_2 = Constants.FrontendDvbtCoderate.CODERATE_1_2; + /** + * 2_3 code rate. + */ + public static final int CODERATE_2_3 = Constants.FrontendDvbtCoderate.CODERATE_2_3; + /** + * 3_4 code rate. + */ + public static final int CODERATE_3_4 = Constants.FrontendDvbtCoderate.CODERATE_3_4; + /** + * 5_6 code rate. + */ + public static final int CODERATE_5_6 = Constants.FrontendDvbtCoderate.CODERATE_5_6; + /** + * 7_8 code rate. + */ + public static final int CODERATE_7_8 = Constants.FrontendDvbtCoderate.CODERATE_7_8; + /** + * 4_5 code rate. + */ + public static final int CODERATE_3_5 = Constants.FrontendDvbtCoderate.CODERATE_3_5; + /** + * 4_5 code rate. + */ + public static final int CODERATE_4_5 = Constants.FrontendDvbtCoderate.CODERATE_4_5; + /** + * 6_7 code rate. + */ + public static final int CODERATE_6_7 = Constants.FrontendDvbtCoderate.CODERATE_6_7; + /** + * 8_9 code rate. + */ + public static final int CODERATE_8_9 = Constants.FrontendDvbtCoderate.CODERATE_8_9; + + /** @hide */ + @IntDef(flag = true, + prefix = "GUARD_INTERVAL_", + value = {GUARD_INTERVAL_UNDEFINED, GUARD_INTERVAL_AUTO, + GUARD_INTERVAL_INTERVAL_1_32, GUARD_INTERVAL_INTERVAL_1_16, + GUARD_INTERVAL_INTERVAL_1_8, GUARD_INTERVAL_INTERVAL_1_4, + GUARD_INTERVAL_INTERVAL_1_128, + GUARD_INTERVAL_INTERVAL_19_128, + GUARD_INTERVAL_INTERVAL_19_256}) + @Retention(RetentionPolicy.SOURCE) + public @interface GuardInterval {} + + /** + * Guard Interval undefined. + */ + public static final int GUARD_INTERVAL_UNDEFINED = + Constants.FrontendDvbtGuardInterval.UNDEFINED; + /** + * Hardware is able to detect and set Guard Interval automatically. + */ + public static final int GUARD_INTERVAL_AUTO = Constants.FrontendDvbtGuardInterval.AUTO; + /** + * 1/32 Guard Interval. + */ + public static final int GUARD_INTERVAL_INTERVAL_1_32 = + Constants.FrontendDvbtGuardInterval.INTERVAL_1_32; + /** + * 1/16 Guard Interval. + */ + public static final int GUARD_INTERVAL_INTERVAL_1_16 = + Constants.FrontendDvbtGuardInterval.INTERVAL_1_16; + /** + * 1/8 Guard Interval. + */ + public static final int GUARD_INTERVAL_INTERVAL_1_8 = + Constants.FrontendDvbtGuardInterval.INTERVAL_1_8; + /** + * 1/4 Guard Interval. + */ + public static final int GUARD_INTERVAL_INTERVAL_1_4 = + Constants.FrontendDvbtGuardInterval.INTERVAL_1_4; + /** + * 1/128 Guard Interval. + */ + public static final int GUARD_INTERVAL_INTERVAL_1_128 = + Constants.FrontendDvbtGuardInterval.INTERVAL_1_128; + /** + * 19/128 Guard Interval. + */ + public static final int GUARD_INTERVAL_INTERVAL_19_128 = + Constants.FrontendDvbtGuardInterval.INTERVAL_19_128; + /** + * 19/256 Guard Interval. + */ + public static final int GUARD_INTERVAL_INTERVAL_19_256 = + Constants.FrontendDvbtGuardInterval.INTERVAL_19_256; + + /** @hide */ + @IntDef(flag = true, + prefix = "STANDARD", + value = {STANDARD_AUTO, STANDARD_T, STANDARD_T2} + ) + @Retention(RetentionPolicy.SOURCE) + public @interface Standard {} + + /** + * Hardware is able to detect and set Standard automatically. + */ + public static final int STANDARD_AUTO = Constants.FrontendDvbtStandard.AUTO; + /** + * T standard. + */ + public static final int STANDARD_T = Constants.FrontendDvbtStandard.T; + /** + * T2 standard. + */ + public static final int STANDARD_T2 = Constants.FrontendDvbtStandard.T2; + + /** @hide */ + @IntDef(flag = true, + prefix = "PLP_MODE_", + value = {PLP_MODE_UNDEFINED, PLP_MODE_AUTO, PLP_MODE_MANUAL}) + @Retention(RetentionPolicy.SOURCE) + public @interface PlpMode {} + + /** + * Physical Layer Pipe (PLP) Mode undefined. + */ + public static final int PLP_MODE_UNDEFINED = Constants.FrontendDvbtPlpMode.UNDEFINED; + /** + * Hardware is able to detect and set Physical Layer Pipe (PLP) Mode automatically. + */ + public static final int PLP_MODE_AUTO = Constants.FrontendDvbtPlpMode.AUTO; + /** + * Physical Layer Pipe (PLP) manual Mode. + */ + public static final int PLP_MODE_MANUAL = Constants.FrontendDvbtPlpMode.MANUAL; + + + private final int mTransmissionMode; + private final int mBandwidth; + private final int mConstellation; + private final int mHierarchy; + private final int mHpCoderate; + private final int mLpCoderate; + private final int mGuardInterval; + private final boolean mIsHighPriority; + private final int mStandard; + private final boolean mIsMiso; + private final int mPlpMode; + private final int mPlpId; + private final int mPlpGroupId; + + private DvbtFrontendSettings(int frequency, int transmissionMode, int bandwidth, + int constellation, int hierarchy, int hpCoderate, int lpCoderate, int guardInterval, + boolean isHighPriority, int standard, boolean isMiso, int plpMode, int plpId, + int plpGroupId) { super(frequency); + mTransmissionMode = transmissionMode; + mBandwidth = bandwidth; + mConstellation = constellation; + mHierarchy = hierarchy; + mHpCoderate = hpCoderate; + mLpCoderate = lpCoderate; + mGuardInterval = guardInterval; + mIsHighPriority = isHighPriority; + mStandard = standard; + mIsMiso = isMiso; + mPlpMode = plpMode; + mPlpId = plpId; + mPlpGroupId = plpGroupId; + } + + /** + * Gets Transmission Mode. + */ + @TransmissionMode + public int getTransmissionMode() { + return mTransmissionMode; + } + /** + * Gets Bandwidth. + */ + @Bandwidth + public int getBandwidth() { + return mBandwidth; + } + /** + * Gets Constellation. + */ + @Constellation + public int getConstellation() { + return mConstellation; + } + /** + * Gets Hierarchy. + */ + @Hierarchy + public int getHierarchy() { + return mHierarchy; + } + /** + * Gets Code Rate for High Priority level. + */ + @Coderate + public int getHpCoderate() { + return mHpCoderate; + } + /** + * Gets Code Rate for Low Priority level. + */ + @Coderate + public int getLpCoderate() { + return mLpCoderate; + } + /** + * Gets Guard Interval. + */ + @GuardInterval + public int getGuardInterval() { + return mGuardInterval; + } + /** + * Checks whether it's high priority. + */ + public boolean isHighPriority() { + return mIsHighPriority; + } + /** + * Gets Standard. + */ + @Standard + public int getStandard() { + return mStandard; + } + /** + * Gets whether it's MISO. + */ + public boolean isMiso() { + return mIsMiso; + } + /** + * Gets Physical Layer Pipe (PLP) Mode. + */ + @PlpMode + public int getPlpMode() { + return mPlpMode; + } + /** + * Gets Physical Layer Pipe (PLP) ID. + */ + public int getPlpId() { + return mPlpId; + } + /** + * Gets Physical Layer Pipe (PLP) group ID. + */ + public int getPlpGroupId() { + return mPlpGroupId; + } + + /** + * Creates a builder for {@link DvbtFrontendSettings}. + * + * @param context the context of the caller. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @NonNull + public static Builder builder(@NonNull Context context) { + TunerUtils.checkTunerPermission(context); + return new Builder(); + } + + /** + * Builder for {@link DvbtFrontendSettings}. + */ + public static class Builder extends FrontendSettings.Builder<Builder> { + private int mTransmissionMode; + private int mBandwidth; + private int mConstellation; + private int mHierarchy; + private int mHpCoderate; + private int mLpCoderate; + private int mGuardInterval; + private boolean mIsHighPriority; + private int mStandard; + private boolean mIsMiso; + private int mPlpMode; + private int mPlpId; + private int mPlpGroupId; + + private Builder() { + } + + /** + * Sets Transmission Mode. + */ + @NonNull + public Builder setTransmissionMode(@TransmissionMode int transmissionMode) { + mTransmissionMode = transmissionMode; + return this; + } + /** + * Sets Bandwidth. + */ + @NonNull + public Builder setBandwidth(@Bandwidth int bandwidth) { + mBandwidth = bandwidth; + return this; + } + /** + * Sets Constellation. + */ + @NonNull + public Builder setConstellation(@Constellation int constellation) { + mConstellation = constellation; + return this; + } + /** + * Sets Hierarchy. + */ + @NonNull + public Builder setHierarchy(@Hierarchy int hierarchy) { + mHierarchy = hierarchy; + return this; + } + /** + * Sets Code Rate for High Priority level. + */ + @NonNull + public Builder setHpCoderate(@Coderate int hpCoderate) { + mHpCoderate = hpCoderate; + return this; + } + /** + * Sets Code Rate for Low Priority level. + */ + @NonNull + public Builder setLpCoderate(@Coderate int lpCoderate) { + mLpCoderate = lpCoderate; + return this; + } + /** + * Sets Guard Interval. + */ + @NonNull + public Builder setGuardInterval(@GuardInterval int guardInterval) { + mGuardInterval = guardInterval; + return this; + } + /** + * Sets whether it's high priority. + */ + @NonNull + public Builder setHighPriority(boolean isHighPriority) { + mIsHighPriority = isHighPriority; + return this; + } + /** + * Sets Standard. + */ + @NonNull + public Builder setStandard(@Standard int standard) { + mStandard = standard; + return this; + } + /** + * Sets whether it's MISO. + */ + @NonNull + public Builder setMiso(boolean isMiso) { + mIsMiso = isMiso; + return this; + } + /** + * Sets Physical Layer Pipe (PLP) Mode. + */ + @NonNull + public Builder setPlpMode(@PlpMode int plpMode) { + mPlpMode = plpMode; + return this; + } + /** + * Sets Physical Layer Pipe (PLP) ID. + */ + @NonNull + public Builder setPlpId(int plpId) { + mPlpId = plpId; + return this; + } + /** + * Sets Physical Layer Pipe (PLP) group ID. + */ + @NonNull + public Builder setPlpGroupId(int plpGroupId) { + mPlpGroupId = plpGroupId; + return this; + } + + /** + * Builds a {@link DvbtFrontendSettings} object. + */ + @NonNull + public DvbtFrontendSettings build() { + return new DvbtFrontendSettings(mFrequency, mTransmissionMode, mBandwidth, + mConstellation, mHierarchy, mHpCoderate, mLpCoderate, mGuardInterval, + mIsHighPriority, mStandard, mIsMiso, mPlpMode, mPlpId, mPlpGroupId); + } + + @Override + Builder self() { + return this; + } } @Override diff --git a/media/java/android/media/tv/tuner/frontend/FrontendCallback.java b/media/java/android/media/tv/tuner/frontend/FrontendCallback.java index 0992eb665330..9c4f4606a9bc 100644 --- a/media/java/android/media/tv/tuner/frontend/FrontendCallback.java +++ b/media/java/android/media/tv/tuner/frontend/FrontendCallback.java @@ -27,10 +27,4 @@ public interface FrontendCallback { * Invoked when there is a frontend event. */ void onEvent(int frontendEventType); - - /** - * Invoked when there is a scan message. - * @param msg - */ - void onScanMessage(ScanMessage msg); } diff --git a/media/java/android/media/tv/tuner/frontend/FrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/FrontendCapabilities.java index 7350bc0c3914..e4f66b80d008 100644 --- a/media/java/android/media/tv/tuner/frontend/FrontendCapabilities.java +++ b/media/java/android/media/tv/tuner/frontend/FrontendCapabilities.java @@ -17,7 +17,8 @@ package android.media.tv.tuner.frontend; /** - * Frontend Capabilities. + * Frontend capabilities. + * * @hide */ public abstract class FrontendCapabilities { diff --git a/media/java/android/media/tv/tuner/frontend/FrontendInfo.java b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java index 99e8dd2fe31f..360c84a383e0 100644 --- a/media/java/android/media/tv/tuner/frontend/FrontendInfo.java +++ b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java @@ -16,77 +16,98 @@ package android.media.tv.tuner.frontend; +import android.annotation.NonNull; import android.media.tv.tuner.frontend.FrontendSettings.Type; +import android.media.tv.tuner.frontend.FrontendStatus.FrontendStatusType; +import android.util.Range; /** - * Frontend info. + * This class is used to specify meta information of a frontend. + * * @hide */ public class FrontendInfo { private final int mId; private final int mType; - private final int mMinFrequency; - private final int mMaxFrequency; - private final int mMinSymbolRate; - private final int mMaxSymbolRate; + private final Range<Integer> mFrequencyRange; + private final Range<Integer> mSymbolRateRange; private final int mAcquireRange; private final int mExclusiveGroupId; private final int[] mStatusCaps; private final FrontendCapabilities mFrontendCap; - FrontendInfo(int id, int type, int minFrequency, int maxFrequency, int minSymbolRate, + private FrontendInfo(int id, int type, int minFrequency, int maxFrequency, int minSymbolRate, int maxSymbolRate, int acquireRange, int exclusiveGroupId, int[] statusCaps, FrontendCapabilities frontendCap) { mId = id; mType = type; - mMinFrequency = minFrequency; - mMaxFrequency = maxFrequency; - mMinSymbolRate = minSymbolRate; - mMaxSymbolRate = maxSymbolRate; + mFrequencyRange = new Range<>(minFrequency, maxFrequency); + mSymbolRateRange = new Range<>(minSymbolRate, maxSymbolRate); mAcquireRange = acquireRange; mExclusiveGroupId = exclusiveGroupId; mStatusCaps = statusCaps; mFrontendCap = frontendCap; } - /** Gets frontend ID. */ + /** + * Gets frontend ID. + */ public int getId() { return mId; } - /** Gets frontend type. */ + /** + * Gets frontend type. + */ @Type public int getType() { return mType; } - /** Gets min frequency. */ - public int getMinFrequency() { - return mMinFrequency; - } - /** Gets max frequency. */ - public int getMaxFrequency() { - return mMaxFrequency; - } - /** Gets min symbol rate. */ - public int getMinSymbolRate() { - return mMinSymbolRate; + + /** + * Gets supported frequency range in Hz. + */ + @NonNull + public Range<Integer> getFrequencyRange() { + return mFrequencyRange; } - /** Gets max symbol rate. */ - public int getMaxSymbolRate() { - return mMaxSymbolRate; + + /** + * Gets symbol rate range in symbols per second. + */ + @NonNull + public Range<Integer> getSymbolRateRange() { + return mSymbolRateRange; } - /** Gets acquire range. */ + + /** + * Gets acquire range in Hz. + * + * <p>The maximum frequency difference the frontend can detect. + */ public int getAcquireRange() { return mAcquireRange; } - /** Gets exclusive group ID. */ + /** + * Gets exclusive group ID. + * + * <p>Frontends with the same exclusive group ID indicates they can't function at same time. For + * instance, they share some hardware modules. + */ public int getExclusiveGroupId() { return mExclusiveGroupId; } - /** Gets status capabilities. */ + /** + * Gets status capabilities. + * + * @return An array of supported status types. + */ + @FrontendStatusType public int[] getStatusCapabilities() { return mStatusCaps; } - /** Gets frontend capability. */ + /** + * Gets frontend capabilities. + */ public FrontendCapabilities getFrontendCapability() { return mFrontendCap; } diff --git a/media/java/android/media/tv/tuner/frontend/FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/FrontendSettings.java index 210aef4163e5..617d6089a610 100644 --- a/media/java/android/media/tv/tuner/frontend/FrontendSettings.java +++ b/media/java/android/media/tv/tuner/frontend/FrontendSettings.java @@ -17,6 +17,8 @@ package android.media.tv.tuner.frontend; import android.annotation.IntDef; +import android.annotation.IntRange; +import android.annotation.NonNull; import android.hardware.tv.tuner.V1_0.Constants; import java.lang.annotation.Retention; @@ -97,4 +99,26 @@ public abstract class FrontendSettings { return mFrequency; } + /** + * Builder for {@link FrontendSettings}. + * + * @param <T> The subclass to be built. + */ + public abstract static class Builder<T extends Builder<T>> { + /* package */ int mFrequency; + + /* package */ Builder() {} + + /** + * Sets frequency in Hz. + */ + @NonNull + @IntRange(from = 1) + public T setFrequency(int frequency) { + mFrequency = frequency; + return self(); + } + + /* package */ abstract T self(); + } } diff --git a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java index fb5d62afd2f2..088adff630a4 100644 --- a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java +++ b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java @@ -16,13 +16,15 @@ package android.media.tv.tuner.frontend; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.hardware.tv.tuner.V1_0.Constants; import android.media.tv.tuner.Lnb; -import android.media.tv.tuner.TunerConstants; -import android.media.tv.tuner.TunerConstants.FrontendDvbcSpectralInversion; -import android.media.tv.tuner.TunerConstants.FrontendDvbtHierarchy; import android.media.tv.tuner.TunerConstants.FrontendInnerFec; import android.media.tv.tuner.TunerConstants.FrontendModulation; -import android.media.tv.tuner.TunerConstants.FrontendStatusType; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * Frontend status @@ -31,204 +33,409 @@ import android.media.tv.tuner.TunerConstants.FrontendStatusType; */ public class FrontendStatus { - private final int mType; - private final Object mValue; + /** @hide */ + @IntDef({FRONTEND_STATUS_TYPE_DEMOD_LOCK, FRONTEND_STATUS_TYPE_SNR, FRONTEND_STATUS_TYPE_BER, + FRONTEND_STATUS_TYPE_PER, FRONTEND_STATUS_TYPE_PRE_BER, + FRONTEND_STATUS_TYPE_SIGNAL_QUALITY, FRONTEND_STATUS_TYPE_SIGNAL_STRENGTH, + FRONTEND_STATUS_TYPE_SYMBOL_RATE, FRONTEND_STATUS_TYPE_FEC, + FRONTEND_STATUS_TYPE_MODULATION, FRONTEND_STATUS_TYPE_SPECTRAL, + FRONTEND_STATUS_TYPE_LNB_VOLTAGE, FRONTEND_STATUS_TYPE_PLP_ID, + FRONTEND_STATUS_TYPE_EWBS, FRONTEND_STATUS_TYPE_AGC, FRONTEND_STATUS_TYPE_LNA, + FRONTEND_STATUS_TYPE_LAYER_ERROR, FRONTEND_STATUS_TYPE_VBER_CN, + FRONTEND_STATUS_TYPE_LBER_CN, FRONTEND_STATUS_TYPE_XER_CN, FRONTEND_STATUS_TYPE_MER, + FRONTEND_STATUS_TYPE_FREQ_OFFSET, FRONTEND_STATUS_TYPE_HIERARCHY, + FRONTEND_STATUS_TYPE_RF_LOCK, FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO}) + @Retention(RetentionPolicy.SOURCE) + public @interface FrontendStatusType {} - private FrontendStatus(int type, Object value) { - mType = type; - mValue = value; - } + /** + * Lock status for Demod. + */ + public static final int FRONTEND_STATUS_TYPE_DEMOD_LOCK = + Constants.FrontendStatusType.DEMOD_LOCK; + /** + * Signal to Noise Ratio. + */ + public static final int FRONTEND_STATUS_TYPE_SNR = Constants.FrontendStatusType.SNR; + /** + * Bit Error Ratio. + */ + public static final int FRONTEND_STATUS_TYPE_BER = Constants.FrontendStatusType.BER; + /** + * Packages Error Ratio. + */ + public static final int FRONTEND_STATUS_TYPE_PER = Constants.FrontendStatusType.PER; + /** + * Bit Error Ratio before FEC. + */ + public static final int FRONTEND_STATUS_TYPE_PRE_BER = Constants.FrontendStatusType.PRE_BER; + /** + * Signal Quality (0..100). Good data over total data in percent can be + * used as a way to present Signal Quality. + */ + public static final int FRONTEND_STATUS_TYPE_SIGNAL_QUALITY = + Constants.FrontendStatusType.SIGNAL_QUALITY; + /** + * Signal Strength. + */ + public static final int FRONTEND_STATUS_TYPE_SIGNAL_STRENGTH = + Constants.FrontendStatusType.SIGNAL_STRENGTH; + /** + * Symbol Rate in symbols per second. + */ + public static final int FRONTEND_STATUS_TYPE_SYMBOL_RATE = + Constants.FrontendStatusType.SYMBOL_RATE; + /** + * Forward Error Correction Type. + */ + public static final int FRONTEND_STATUS_TYPE_FEC = Constants.FrontendStatusType.FEC; + /** + * Modulation Type. + */ + public static final int FRONTEND_STATUS_TYPE_MODULATION = + Constants.FrontendStatusType.MODULATION; + /** + * Spectral Inversion Type. + */ + public static final int FRONTEND_STATUS_TYPE_SPECTRAL = Constants.FrontendStatusType.SPECTRAL; + /** + * LNB Voltage. + */ + public static final int FRONTEND_STATUS_TYPE_LNB_VOLTAGE = + Constants.FrontendStatusType.LNB_VOLTAGE; + /** + * Physical Layer Pipe ID. + */ + public static final int FRONTEND_STATUS_TYPE_PLP_ID = Constants.FrontendStatusType.PLP_ID; + /** + * Status for Emergency Warning Broadcasting System. + */ + public static final int FRONTEND_STATUS_TYPE_EWBS = Constants.FrontendStatusType.EWBS; + /** + * Automatic Gain Control. + */ + public static final int FRONTEND_STATUS_TYPE_AGC = Constants.FrontendStatusType.AGC; + /** + * Low Noise Amplifier. + */ + public static final int FRONTEND_STATUS_TYPE_LNA = Constants.FrontendStatusType.LNA; + /** + * Error status by layer. + */ + public static final int FRONTEND_STATUS_TYPE_LAYER_ERROR = + Constants.FrontendStatusType.LAYER_ERROR; + /** + * CN value by VBER. + */ + public static final int FRONTEND_STATUS_TYPE_VBER_CN = Constants.FrontendStatusType.VBER_CN; + /** + * CN value by LBER. + */ + public static final int FRONTEND_STATUS_TYPE_LBER_CN = Constants.FrontendStatusType.LBER_CN; + /** + * CN value by XER. + */ + public static final int FRONTEND_STATUS_TYPE_XER_CN = Constants.FrontendStatusType.XER_CN; + /** + * Modulation Error Ratio. + */ + public static final int FRONTEND_STATUS_TYPE_MER = Constants.FrontendStatusType.MER; + /** + * Difference between tuning frequency and actual locked frequency. + */ + public static final int FRONTEND_STATUS_TYPE_FREQ_OFFSET = + Constants.FrontendStatusType.FREQ_OFFSET; + /** + * Hierarchy for DVBT. + */ + public static final int FRONTEND_STATUS_TYPE_HIERARCHY = Constants.FrontendStatusType.HIERARCHY; + /** + * Lock status for RF. + */ + public static final int FRONTEND_STATUS_TYPE_RF_LOCK = Constants.FrontendStatusType.RF_LOCK; + /** + * PLP information in a frequency band for ATSC-3.0 frontend. + */ + public static final int FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO = + Constants.FrontendStatusType.ATSC3_PLP_INFO; - /** Gets frontend status type. */ - @FrontendStatusType - public int getStatusType() { - return mType; + + private Boolean mIsDemodLocked; + private Integer mSnr; + private Integer mBer; + private Integer mPer; + private Integer mPerBer; + private Integer mSignalQuality; + private Integer mSignalStrength; + private Integer mSymbolRate; + private Long mInnerFec; + private Integer mModulation; + private Integer mInversion; + private Integer mLnbVoltage; + private Integer mPlpId; + private Boolean mIsEwbs; + private Integer mAgc; + private Boolean mIsLnaOn; + private boolean[] mIsLayerErrors; + private Integer mVberCn; + private Integer mLberCn; + private Integer mXerCn; + private Integer mMer; + private Integer mFreqOffset; + private Integer mHierarchy; + private Boolean mIsRfLocked; + private Atsc3PlpInfo[] mPlpInfo; + + // Constructed and fields set by JNI code. + private FrontendStatus() { } - /** Lock status for Demod in True/False. */ - public boolean getIsDemodLocked() { - if (mType != TunerConstants.FRONTEND_STATUS_TYPE_DEMOD_LOCK) { + + /** + * Lock status for Demod. + */ + public boolean isDemodLocked() { + if (mIsDemodLocked == null) { throw new IllegalStateException(); } - return (Boolean) mValue; + return mIsDemodLocked; } - /** SNR value measured by 0.001 dB. */ + /** + * Gets Signal to Noise Ratio in thousandths of a deciBel (0.001dB). + */ public int getSnr() { - if (mType != TunerConstants.FRONTEND_STATUS_TYPE_SNR) { + if (mSnr == null) { throw new IllegalStateException(); } - return (int) mValue; + return mSnr; } - /** The number of error bit per 1 billion bits. */ + /** + * Gets Bit Error Ratio. + * + * <p>The number of error bit per 1 billion bits. + */ public int getBer() { - if (mType != TunerConstants.FRONTEND_STATUS_TYPE_BER) { + if (mBer == null) { throw new IllegalStateException(); } - return (int) mValue; + return mBer; } - /** The number of error package per 1 billion packages. */ + + /** + * Gets Packages Error Ratio. + * + * <p>The number of error package per 1 billion packages. + */ public int getPer() { - if (mType != TunerConstants.FRONTEND_STATUS_TYPE_PER) { + if (mPer == null) { throw new IllegalStateException(); } - return (int) mValue; + return mPer; } - /** The number of error bit per 1 billion bits before FEC. */ + /** + * Gets Bit Error Ratio before Forward Error Correction (FEC). + * + * <p>The number of error bit per 1 billion bits before FEC. + */ public int getPerBer() { - if (mType != TunerConstants.FRONTEND_STATUS_TYPE_PRE_BER) { + if (mPerBer == null) { throw new IllegalStateException(); } - return (int) mValue; + return mPerBer; } - /** Signal Quality in percent. */ + /** + * Gets Signal Quality in percent. + */ public int getSignalQuality() { - if (mType != TunerConstants.FRONTEND_STATUS_TYPE_SIGNAL_QUALITY) { + if (mSignalQuality == null) { throw new IllegalStateException(); } - return (int) mValue; + return mSignalQuality; } - /** Signal Strength measured by 0.001 dBm. */ + /** + * Gets Signal Strength in thousandths of a dBm (0.001dBm). + */ public int getSignalStrength() { - if (mType != TunerConstants.FRONTEND_STATUS_TYPE_SIGNAL_STRENGTH) { + if (mSignalStrength == null) { throw new IllegalStateException(); } - return (int) mValue; + return mSignalStrength; } - /** Symbols per second. */ + /** + * Gets symbol rate in symbols per second. + */ public int getSymbolRate() { - if (mType != TunerConstants.FRONTEND_STATUS_TYPE_SYMBOL_RATE) { + if (mSymbolRate == null) { throw new IllegalStateException(); } - return (int) mValue; + return mSymbolRate; } /** - * Inner Forward Error Correction type as specified in ETSI EN 300 468 V1.15.1 + * Gets Inner Forward Error Correction type as specified in ETSI EN 300 468 V1.15.1 * and ETSI EN 302 307-2 V1.1.1. */ @FrontendInnerFec public long getFec() { - if (mType != TunerConstants.FRONTEND_STATUS_TYPE_FEC) { + if (mInnerFec == null) { throw new IllegalStateException(); } - return (long) mValue; + return mInnerFec; } - /** Modulation */ + /** + * Gets modulation. + */ @FrontendModulation public int getModulation() { - if (mType != TunerConstants.FRONTEND_STATUS_TYPE_MODULATION) { + if (mModulation == null) { throw new IllegalStateException(); } - return (int) mValue; + return mModulation; } - /** Spectral Inversion for DVBC. */ - @FrontendDvbcSpectralInversion + /** + * Gets Spectral Inversion for DVBC. + */ + @DvbcFrontendSettings.SpectralInversion public int getSpectralInversion() { - if (mType != TunerConstants.FRONTEND_STATUS_TYPE_SPECTRAL) { + if (mInversion == null) { throw new IllegalStateException(); } - return (int) mValue; + return mInversion; } - /** Power Voltage Type for LNB. */ + /** + * Gets Power Voltage Type for LNB. + */ @Lnb.Voltage public int getLnbVoltage() { - if (mType != TunerConstants.FRONTEND_STATUS_TYPE_LNB_VOLTAGE) { + if (mLnbVoltage == null) { throw new IllegalStateException(); } - return (int) mValue; + return mLnbVoltage; } - /** PLP ID */ - public byte getPlpId() { - if (mType != TunerConstants.FRONTEND_STATUS_TYPE_PLP_ID) { + /** + * Gets Physical Layer Pipe ID. + */ + public int getPlpId() { + if (mPlpId == null) { throw new IllegalStateException(); } - return (byte) mValue; + return mPlpId; } - /** Emergency Warning Broadcasting System */ - public boolean getIsEwbs() { - if (mType != TunerConstants.FRONTEND_STATUS_TYPE_EWBS) { + /** + * Checks whether it's Emergency Warning Broadcasting System + */ + public boolean isEwbs() { + if (mIsEwbs == null) { throw new IllegalStateException(); } - return (Boolean) mValue; + return mIsEwbs; } - /** AGC value is normalized from 0 to 255. */ - public byte getAgc() { - if (mType != TunerConstants.FRONTEND_STATUS_TYPE_AGC) { + /** + * Gets Automatic Gain Control value which is normalized from 0 to 255. + */ + public int getAgc() { + if (mAgc == null) { throw new IllegalStateException(); } - return (byte) mValue; + return mAgc; } - /** LNA(Low Noise Amplifier) is on or not. */ - public boolean getLnaOn() { - if (mType != TunerConstants.FRONTEND_STATUS_TYPE_LNA) { + /** + * Checks LNA (Low Noise Amplifier) is on or not. + */ + public boolean isLnaOn() { + if (mIsLnaOn == null) { throw new IllegalStateException(); } - return (Boolean) mValue; + return mIsLnaOn; } - /** Error status by layer. */ - public boolean[] getIsLayerError() { - if (mType != TunerConstants.FRONTEND_STATUS_TYPE_LAYER_ERROR) { + /** + * Gets Error status by layer. + */ + @NonNull + public boolean[] getLayerErrors() { + if (mIsLayerErrors == null) { throw new IllegalStateException(); } - return (boolean[]) mValue; + return mIsLayerErrors; } - /** CN value by VBER measured by 0.001 dB. */ + /** + * Gets CN value by VBER in thousandths of a deciBel (0.001dB). + */ public int getVberCn() { - if (mType != TunerConstants.FRONTEND_STATUS_TYPE_VBER_CN) { + if (mVberCn == null) { throw new IllegalStateException(); } - return (int) mValue; + return mVberCn; } - /** CN value by LBER measured by 0.001 dB. */ + /** + * Gets CN value by LBER in thousandths of a deciBel (0.001dB). + */ public int getLberCn() { - if (mType != TunerConstants.FRONTEND_STATUS_TYPE_LBER_CN) { + if (mLberCn == null) { throw new IllegalStateException(); } - return (int) mValue; + return mLberCn; } - /** CN value by XER measured by 0.001 dB. */ + /** + * Gets CN value by XER in thousandths of a deciBel (0.001dB). + */ public int getXerCn() { - if (mType != TunerConstants.FRONTEND_STATUS_TYPE_XER_CN) { + if (mXerCn == null) { throw new IllegalStateException(); } - return (int) mValue; + return mXerCn; } - /** MER value measured by 0.001 dB. */ + /** + * Gets Modulation Error Ratio in thousandths of a deciBel (0.001dB). + */ public int getMer() { - if (mType != TunerConstants.FRONTEND_STATUS_TYPE_MER) { + if (mMer == null) { throw new IllegalStateException(); } - return (int) mValue; + return mMer; } - /** Frequency difference in Hertz. */ + /** + * Gets frequency difference in Hz. + * + * <p>Difference between tuning frequency and actual locked frequency. + */ public int getFreqOffset() { - if (mType != TunerConstants.FRONTEND_STATUS_TYPE_FREQ_OFFSET) { + if (mFreqOffset == null) { throw new IllegalStateException(); } - return (int) mValue; + return mFreqOffset; } - /** Hierarchy Type for DVBT. */ - @FrontendDvbtHierarchy + /** + * Gets hierarchy Type for DVBT. + */ + @DvbtFrontendSettings.Hierarchy public int getHierarchy() { - if (mType != TunerConstants.FRONTEND_STATUS_TYPE_HIERARCHY) { + if (mHierarchy == null) { throw new IllegalStateException(); } - return (int) mValue; + return mHierarchy; } - /** Lock status for RF. */ - public boolean getIsRfLock() { - if (mType != TunerConstants.FRONTEND_STATUS_TYPE_RF_LOCK) { + /** + * Gets lock status for RF. + */ + public boolean isRfLock() { + if (mIsRfLocked == null) { throw new IllegalStateException(); } - return (Boolean) mValue; + return mIsRfLocked; } - /** A list of PLP status for tuned PLPs for ATSC3 frontend. */ + /** + * Gets an array of PLP status for tuned PLPs for ATSC3 frontend. + */ + @NonNull public Atsc3PlpInfo[] getAtsc3PlpInfo() { - if (mType != TunerConstants.FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO) { + if (mPlpInfo == null) { throw new IllegalStateException(); } - return (Atsc3PlpInfo[]) mValue; + return mPlpInfo; } - /** Status for each tuning PLPs. */ + /** + * Status for each tuning Physical Layer Pipes. + */ public static class Atsc3PlpInfo { private final int mPlpId; private final boolean mIsLock; @@ -240,17 +447,20 @@ public class FrontendStatus { mUec = uec; } - /** Gets PLP IDs. */ + /** + * Gets Physical Layer Pipe ID. + */ public int getPlpId() { return mPlpId; } - /** Gets Demod Lock/Unlock status of this particular PLP. */ - public boolean getIsLock() { + /** + * Gets Demod Lock/Unlock status of this particular PLP. + */ + public boolean isLock() { return mIsLock; } /** - * Gets Uncorrectable Error Counts (UEC) of this particular PLP since last tune - * operation. + * Gets Uncorrectable Error Counts (UEC) of this particular PLP since last tune operation. */ public int getUec() { return mUec; diff --git a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendCapabilities.java index 92832b7fcbdd..61cba1c1e4f3 100644 --- a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendCapabilities.java +++ b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendCapabilities.java @@ -24,16 +24,22 @@ public class Isdbs3FrontendCapabilities extends FrontendCapabilities { private final int mModulationCap; private final int mCoderateCap; - Isdbs3FrontendCapabilities(int modulationCap, int coderateCap) { + private Isdbs3FrontendCapabilities(int modulationCap, int coderateCap) { mModulationCap = modulationCap; mCoderateCap = coderateCap; } - /** Gets modulation capability. */ + /** + * Gets modulation capability. + */ + @Isdbs3FrontendSettings.Modulation public int getModulationCapability() { return mModulationCap; } - /** Gets code rate capability. */ + /** + * Gets code rate capability. + */ + @Isdbs3FrontendSettings.Coderate public int getCodeRateCapability() { return mCoderateCap; } diff --git a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java index 45932a74a946..7e6f1888cc4e 100644 --- a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java +++ b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java @@ -16,20 +16,284 @@ package android.media.tv.tuner.frontend; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.content.Context; +import android.hardware.tv.tuner.V1_0.Constants; +import android.media.tv.tuner.TunerUtils; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Frontend settings for ISDBS-3. * @hide */ public class Isdbs3FrontendSettings extends FrontendSettings { - public int streamId; - public int streamIdType; - public int modulation; - public int coderate; - public int symbolRate; - public int rolloff; - - Isdbs3FrontendSettings(int frequency) { + /** @hide */ + @IntDef(flag = true, + prefix = "MODULATION_", + value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_BPSK, + MODULATION_MOD_QPSK, MODULATION_MOD_8PSK, MODULATION_MOD_16APSK, + MODULATION_MOD_32APSK}) + @Retention(RetentionPolicy.SOURCE) + public @interface Modulation {} + + /** + * Modulation undefined. + */ + public static final int MODULATION_UNDEFINED = Constants.FrontendIsdbs3Modulation.UNDEFINED; + /** + * Hardware is able to detect and set modulation automatically. + */ + public static final int MODULATION_AUTO = Constants.FrontendIsdbs3Modulation.AUTO; + /** + * BPSK Modulation. + */ + public static final int MODULATION_MOD_BPSK = Constants.FrontendIsdbs3Modulation.MOD_BPSK; + /** + * QPSK Modulation. + */ + public static final int MODULATION_MOD_QPSK = Constants.FrontendIsdbs3Modulation.MOD_QPSK; + /** + * 8PSK Modulation. + */ + public static final int MODULATION_MOD_8PSK = Constants.FrontendIsdbs3Modulation.MOD_8PSK; + /** + * 16APSK Modulation. + */ + public static final int MODULATION_MOD_16APSK = Constants.FrontendIsdbs3Modulation.MOD_16APSK; + /** + * 32APSK Modulation. + */ + public static final int MODULATION_MOD_32APSK = Constants.FrontendIsdbs3Modulation.MOD_32APSK; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, + prefix = "CODERATE_", + value = {CODERATE_UNDEFINED, CODERATE_AUTO, CODERATE_1_3, CODERATE_2_5, CODERATE_1_2, + CODERATE_3_5, CODERATE_2_3, CODERATE_3_4, CODERATE_7_9, CODERATE_4_5, + CODERATE_5_6, CODERATE_7_8, CODERATE_9_10}) + public @interface Coderate {} + + /** + * Code rate undefined. + */ + public static final int CODERATE_UNDEFINED = Constants.FrontendIsdbs3Coderate.UNDEFINED; + /** + * Hardware is able to detect and set code rate automatically. + */ + public static final int CODERATE_AUTO = Constants.FrontendIsdbs3Coderate.AUTO; + /** + * 1_3 code rate. + */ + public static final int CODERATE_1_3 = Constants.FrontendIsdbs3Coderate.CODERATE_1_3; + /** + * 2_5 code rate. + */ + public static final int CODERATE_2_5 = Constants.FrontendIsdbs3Coderate.CODERATE_2_5; + /** + * 1_2 code rate. + */ + public static final int CODERATE_1_2 = Constants.FrontendIsdbs3Coderate.CODERATE_1_2; + /** + * 3_5 code rate. + */ + public static final int CODERATE_3_5 = Constants.FrontendIsdbs3Coderate.CODERATE_3_5; + /** + * 2_3 code rate. + */ + public static final int CODERATE_2_3 = Constants.FrontendIsdbs3Coderate.CODERATE_2_3; + /** + * 3_4 code rate. + */ + public static final int CODERATE_3_4 = Constants.FrontendIsdbs3Coderate.CODERATE_3_4; + /** + * 7_9 code rate. + */ + public static final int CODERATE_7_9 = Constants.FrontendIsdbs3Coderate.CODERATE_7_9; + /** + * 4_5 code rate. + */ + public static final int CODERATE_4_5 = Constants.FrontendIsdbs3Coderate.CODERATE_4_5; + /** + * 5_6 code rate. + */ + public static final int CODERATE_5_6 = Constants.FrontendIsdbs3Coderate.CODERATE_5_6; + /** + * 7_8 code rate. + */ + public static final int CODERATE_7_8 = Constants.FrontendIsdbs3Coderate.CODERATE_7_8; + /** + * 9_10 code rate. + */ + public static final int CODERATE_9_10 = Constants.FrontendIsdbs3Coderate.CODERATE_9_10; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "ROLLOFF_", + value = {ROLLOFF_UNDEFINED, ROLLOFF_0_03}) + public @interface Rolloff {} + + /** + * Roll off type undefined. + */ + public static final int ROLLOFF_UNDEFINED = Constants.FrontendIsdbs3Rolloff.UNDEFINED; + /** + * 0.03 roll off type. + */ + public static final int ROLLOFF_0_03 = Constants.FrontendIsdbs3Rolloff.ROLLOFF_0_03; + + + private final int mStreamId; + private final int mStreamIdType; + private final int mModulation; + private final int mCoderate; + private final int mSymbolRate; + private final int mRolloff; + + private Isdbs3FrontendSettings(int frequency, int streamId, int streamIdType, int modulation, + int coderate, int symbolRate, int rolloff) { super(frequency); + mStreamId = streamId; + mStreamIdType = streamIdType; + mModulation = modulation; + mCoderate = coderate; + mSymbolRate = symbolRate; + mRolloff = rolloff; + } + + /** + * Gets Stream ID. + */ + public int getStreamId() { + return mStreamId; + } + /** + * Gets Stream ID Type. + */ + @IsdbsFrontendSettings.StreamIdType + public int getStreamIdType() { + return mStreamIdType; + } + /** + * Gets Modulation. + */ + @Modulation + public int getModulation() { + return mModulation; + } + /** + * Gets Code rate. + */ + @Coderate + public int getCoderate() { + return mCoderate; + } + /** + * Gets Symbol Rate in symbols per second. + */ + public int getSymbolRate() { + return mSymbolRate; + } + /** + * Gets Roll off type. + */ + @Rolloff + public int getRolloff() { + return mRolloff; + } + + /** + * Creates a builder for {@link Isdbs3FrontendSettings}. + * + * @param context the context of the caller. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @NonNull + public static Builder builder(@NonNull Context context) { + TunerUtils.checkTunerPermission(context); + return new Builder(); + } + + /** + * Builder for {@link Isdbs3FrontendSettings}. + */ + public static class Builder extends FrontendSettings.Builder<Builder> { + private int mStreamId; + private int mStreamIdType; + private int mModulation; + private int mCoderate; + private int mSymbolRate; + private int mRolloff; + + private Builder() { + } + + /** + * Sets Stream ID. + */ + @NonNull + public Builder setStreamId(int streamId) { + mStreamId = streamId; + return this; + } + /** + * Sets StreamIdType. + */ + @NonNull + public Builder setStreamIdType(@IsdbsFrontendSettings.StreamIdType int streamIdType) { + mStreamIdType = streamIdType; + return this; + } + /** + * Sets Modulation. + */ + @NonNull + public Builder setModulation(@Modulation int modulation) { + mModulation = modulation; + return this; + } + /** + * Sets Code rate. + */ + @NonNull + public Builder setCoderate(@Coderate int coderate) { + mCoderate = coderate; + return this; + } + /** + * Sets Symbol Rate in symbols per second. + */ + @NonNull + public Builder setSymbolRate(int symbolRate) { + mSymbolRate = symbolRate; + return this; + } + /** + * Sets Roll off type. + */ + @NonNull + public Builder setRolloff(@Rolloff int rolloff) { + mRolloff = rolloff; + return this; + } + + /** + * Builds a {@link Isdbs3FrontendSettings} object. + */ + @NonNull + public Isdbs3FrontendSettings build() { + return new Isdbs3FrontendSettings(mFrequency, mStreamId, mStreamIdType, mModulation, + mCoderate, mSymbolRate, mRolloff); + } + + @Override + Builder self() { + return this; + } } @Override diff --git a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendCapabilities.java index b930b2578092..8e5ecc4c3f59 100644 --- a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendCapabilities.java +++ b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendCapabilities.java @@ -24,16 +24,22 @@ public class IsdbsFrontendCapabilities extends FrontendCapabilities { private final int mModulationCap; private final int mCoderateCap; - IsdbsFrontendCapabilities(int modulationCap, int coderateCap) { + private IsdbsFrontendCapabilities(int modulationCap, int coderateCap) { mModulationCap = modulationCap; mCoderateCap = coderateCap; } - /** Gets modulation capability. */ + /** + * Gets modulation capability. + */ + @IsdbsFrontendSettings.Modulation public int getModulationCapability() { return mModulationCap; } - /** Gets code rate capability. */ + /** + * Gets code rate capability. + */ + @IsdbsFrontendSettings.Coderate public int getCodeRateCapability() { return mCoderateCap; } diff --git a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java index e726a9a701f0..fe100f8ecdd3 100644 --- a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java +++ b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java @@ -16,20 +16,269 @@ package android.media.tv.tuner.frontend; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.content.Context; +import android.hardware.tv.tuner.V1_0.Constants; +import android.media.tv.tuner.TunerUtils; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Frontend settings for ISDBS. * @hide */ public class IsdbsFrontendSettings extends FrontendSettings { - public int streamId; - public int streamIdType; - public int modulation; - public int coderate; - public int symbolRate; - public int rolloff; - - IsdbsFrontendSettings(int frequency) { + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "STREAM_ID_TYPE_", + value = {STREAM_ID_TYPE_ID, STREAM_ID_TYPE_RELATIVE_NUMBER}) + public @interface StreamIdType {} + + /** + * Uses stream ID. + */ + public static final int STREAM_ID_TYPE_ID = Constants.FrontendIsdbsStreamIdType.STREAM_ID; + /** + * Uses relative number. + */ + public static final int STREAM_ID_TYPE_RELATIVE_NUMBER = + Constants.FrontendIsdbsStreamIdType.RELATIVE_STREAM_NUMBER; + + + /** @hide */ + @IntDef(flag = true, + prefix = "MODULATION_", + value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_BPSK, + MODULATION_MOD_QPSK, MODULATION_MOD_TC8PSK}) + @Retention(RetentionPolicy.SOURCE) + public @interface Modulation {} + + /** + * Modulation undefined. + */ + public static final int MODULATION_UNDEFINED = Constants.FrontendIsdbsModulation.UNDEFINED; + /** + * Hardware is able to detect and set modulation automatically + */ + public static final int MODULATION_AUTO = Constants.FrontendIsdbsModulation.AUTO; + /** + * BPSK Modulation. + */ + public static final int MODULATION_MOD_BPSK = Constants.FrontendIsdbsModulation.MOD_BPSK; + /** + * QPSK Modulation. + */ + public static final int MODULATION_MOD_QPSK = Constants.FrontendIsdbsModulation.MOD_QPSK; + /** + * TC8PSK Modulation. + */ + public static final int MODULATION_MOD_TC8PSK = Constants.FrontendIsdbsModulation.MOD_TC8PSK; + + + /** @hide */ + @IntDef(flag = true, + prefix = "CODERATE_", + value = {CODERATE_UNDEFINED, CODERATE_AUTO, + CODERATE_1_2, CODERATE_2_3, CODERATE_3_4, + CODERATE_5_6, CODERATE_7_8}) + @Retention(RetentionPolicy.SOURCE) + public @interface Coderate {} + + /** + * Code rate undefined. + */ + public static final int CODERATE_UNDEFINED = Constants.FrontendIsdbsCoderate.UNDEFINED; + /** + * Hardware is able to detect and set code rate automatically. + */ + public static final int CODERATE_AUTO = Constants.FrontendIsdbsCoderate.AUTO; + /** + * 1_2 code rate. + */ + public static final int CODERATE_1_2 = Constants.FrontendIsdbsCoderate.CODERATE_1_2; + /** + * 2_3 code rate. + */ + public static final int CODERATE_2_3 = Constants.FrontendIsdbsCoderate.CODERATE_2_3; + /** + * 3_4 code rate. + */ + public static final int CODERATE_3_4 = Constants.FrontendIsdbsCoderate.CODERATE_3_4; + /** + * 5_6 code rate. + */ + public static final int CODERATE_5_6 = Constants.FrontendIsdbsCoderate.CODERATE_5_6; + /** + * 7_8 code rate. + */ + public static final int CODERATE_7_8 = Constants.FrontendIsdbsCoderate.CODERATE_7_8; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "ROLLOFF_", + value = {ROLLOFF_UNDEFINED, ROLLOFF_0_35}) + public @interface Rolloff {} + + /** + * Roll off type undefined. + */ + public static final int ROLLOFF_UNDEFINED = Constants.FrontendIsdbs3Rolloff.UNDEFINED; + /** + * 0.35 roll off type. + */ + public static final int ROLLOFF_0_35 = Constants.FrontendIsdbsRolloff.ROLLOFF_0_35; + + + private final int mStreamId; + private final int mStreamIdType; + private final int mModulation; + private final int mCoderate; + private final int mSymbolRate; + private final int mRolloff; + + private IsdbsFrontendSettings(int frequency, int streamId, int streamIdType, int modulation, + int coderate, int symbolRate, int rolloff) { super(frequency); + mStreamId = streamId; + mStreamIdType = streamIdType; + mModulation = modulation; + mCoderate = coderate; + mSymbolRate = symbolRate; + mRolloff = rolloff; + } + + /** + * Gets Stream ID. + */ + public int getStreamId() { + return mStreamId; + } + /** + * Gets Stream ID Type. + */ + @StreamIdType + public int getStreamIdType() { + return mStreamIdType; + } + /** + * Gets Modulation. + */ + @Modulation + public int getModulation() { + return mModulation; + } + /** + * Gets Code rate. + */ + @Coderate + public int getCoderate() { + return mCoderate; + } + /** + * Gets Symbol Rate in symbols per second. + */ + public int getSymbolRate() { + return mSymbolRate; + } + /** + * Gets Roll off type. + */ + @Rolloff + public int getRolloff() { + return mRolloff; + } + + /** + * Creates a builder for {@link IsdbsFrontendSettings}. + * + * @param context the context of the caller. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @NonNull + public static Builder builder(@NonNull Context context) { + TunerUtils.checkTunerPermission(context); + return new Builder(); + } + + /** + * Builder for {@link IsdbsFrontendSettings}. + */ + public static class Builder extends FrontendSettings.Builder<Builder> { + private int mStreamId; + private int mStreamIdType; + private int mModulation; + private int mCoderate; + private int mSymbolRate; + private int mRolloff; + + private Builder() { + } + + /** + * Sets Stream ID. + */ + @NonNull + public Builder setStreamId(int streamId) { + mStreamId = streamId; + return this; + } + /** + * Sets StreamIdType. + */ + @NonNull + public Builder setStreamIdType(@StreamIdType int streamIdType) { + mStreamIdType = streamIdType; + return this; + } + /** + * Sets Modulation. + */ + @NonNull + public Builder setModulation(@Modulation int modulation) { + mModulation = modulation; + return this; + } + /** + * Sets Code rate. + */ + @NonNull + public Builder setCoderate(@Coderate int coderate) { + mCoderate = coderate; + return this; + } + /** + * Sets Symbol Rate in symbols per second. + */ + @NonNull + public Builder setSymbolRate(int symbolRate) { + mSymbolRate = symbolRate; + return this; + } + /** + * Sets Roll off type. + */ + @NonNull + public Builder setRolloff(@Rolloff int rolloff) { + mRolloff = rolloff; + return this; + } + + /** + * Builds a {@link IsdbsFrontendSettings} object. + */ + @NonNull + public IsdbsFrontendSettings build() { + return new IsdbsFrontendSettings(mFrequency, mStreamId, mStreamIdType, mModulation, + mCoderate, mSymbolRate, mRolloff); + } + + @Override + Builder self() { + return this; + } } @Override diff --git a/media/java/android/media/tv/tuner/frontend/IsdbcFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendCapabilities.java index 6544b17609c2..19f04de6ef39 100644 --- a/media/java/android/media/tv/tuner/frontend/IsdbcFrontendCapabilities.java +++ b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendCapabilities.java @@ -17,18 +17,18 @@ package android.media.tv.tuner.frontend; /** - * ISDBC Capabilities. + * ISDBT Capabilities. * @hide */ -public class IsdbcFrontendCapabilities extends FrontendCapabilities { +public class IsdbtFrontendCapabilities extends FrontendCapabilities { private final int mModeCap; private final int mBandwidthCap; private final int mModulationCap; private final int mCoderateCap; private final int mGuardIntervalCap; - IsdbcFrontendCapabilities(int modeCap, int bandwidthCap, int modulationCap, int coderateCap, - int guardIntervalCap) { + private IsdbtFrontendCapabilities(int modeCap, int bandwidthCap, int modulationCap, + int coderateCap, int guardIntervalCap) { mModeCap = modeCap; mBandwidthCap = bandwidthCap; mModulationCap = modulationCap; @@ -36,23 +36,38 @@ public class IsdbcFrontendCapabilities extends FrontendCapabilities { mGuardIntervalCap = guardIntervalCap; } - /** Gets mode capability. */ + /** + * Gets mode capability. + */ + @IsdbtFrontendSettings.Mode public int getModeCapability() { return mModeCap; } - /** Gets bandwidth capability. */ + /** + * Gets bandwidth capability. + */ + @IsdbtFrontendSettings.Bandwidth public int getBandwidthCapability() { return mBandwidthCap; } - /** Gets modulation capability. */ + /** + * Gets modulation capability. + */ + @IsdbtFrontendSettings.Modulation public int getModulationCapability() { return mModulationCap; } - /** Gets code rate capability. */ + /** + * Gets code rate capability. + */ + @DvbtFrontendSettings.Coderate public int getCodeRateCapability() { return mCoderateCap; } - /** Gets guard interval capability. */ + /** + * Gets guard interval capability. + */ + @DvbtFrontendSettings.GuardInterval public int getGuardIntervalCapability() { return mGuardIntervalCap; } diff --git a/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java index f2b7d2413911..15101930e440 100644 --- a/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java +++ b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java @@ -16,19 +16,243 @@ package android.media.tv.tuner.frontend; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.content.Context; +import android.hardware.tv.tuner.V1_0.Constants; +import android.media.tv.tuner.TunerUtils; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Frontend settings for ISDBT. * @hide */ public class IsdbtFrontendSettings extends FrontendSettings { - public int modulation; - public int bandwidth; - public int coderate; - public int guardInterval; - public int serviceAreaId; + /** @hide */ + @IntDef(flag = true, + prefix = "MODULATION_", + value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_DQPSK, + MODULATION_MOD_QPSK, MODULATION_MOD_16QAM, MODULATION_MOD_64QAM}) + @Retention(RetentionPolicy.SOURCE) + public @interface Modulation {} + + /** + * Modulation undefined. + */ + public static final int MODULATION_UNDEFINED = Constants.FrontendIsdbtModulation.UNDEFINED; + /** + * Hardware is able to detect and set modulation automatically + */ + public static final int MODULATION_AUTO = Constants.FrontendIsdbtModulation.AUTO; + /** + * DQPSK Modulation. + */ + public static final int MODULATION_MOD_DQPSK = Constants.FrontendIsdbtModulation.MOD_DQPSK; + /** + * QPSK Modulation. + */ + public static final int MODULATION_MOD_QPSK = Constants.FrontendIsdbtModulation.MOD_QPSK; + /** + * 16QAM Modulation. + */ + public static final int MODULATION_MOD_16QAM = Constants.FrontendIsdbtModulation.MOD_16QAM; + /** + * 64QAM Modulation. + */ + public static final int MODULATION_MOD_64QAM = Constants.FrontendIsdbtModulation.MOD_64QAM; + + + /** @hide */ + @IntDef(flag = true, + prefix = "MODE_", + value = {MODE_UNDEFINED, MODE_AUTO, MODE_1, MODE_2, MODE_3}) + @Retention(RetentionPolicy.SOURCE) + public @interface Mode {} + + /** + * Mode undefined. + */ + public static final int MODE_UNDEFINED = Constants.FrontendIsdbtMode.UNDEFINED; + /** + * Hardware is able to detect and set Mode automatically. + */ + public static final int MODE_AUTO = Constants.FrontendIsdbtMode.AUTO; + /** + * Mode 1 + */ + public static final int MODE_1 = Constants.FrontendIsdbtMode.MODE_1; + /** + * Mode 2 + */ + public static final int MODE_2 = Constants.FrontendIsdbtMode.MODE_2; + /** + * Mode 3 + */ + public static final int MODE_3 = Constants.FrontendIsdbtMode.MODE_3; - IsdbtFrontendSettings(int frequency) { + + /** @hide */ + @IntDef(flag = true, + prefix = "BANDWIDTH_", + value = {BANDWIDTH_UNDEFINED, BANDWIDTH_AUTO, BANDWIDTH_8MHZ, BANDWIDTH_7MHZ, + BANDWIDTH_6MHZ}) + @Retention(RetentionPolicy.SOURCE) + public @interface Bandwidth {} + + /** + * Bandwidth undefined. + */ + public static final int BANDWIDTH_UNDEFINED = Constants.FrontendIsdbtBandwidth.UNDEFINED; + /** + * Hardware is able to detect and set Bandwidth automatically. + */ + public static final int BANDWIDTH_AUTO = Constants.FrontendIsdbtBandwidth.AUTO; + /** + * 8 MHz bandwidth. + */ + public static final int BANDWIDTH_8MHZ = Constants.FrontendIsdbtBandwidth.BANDWIDTH_8MHZ; + /** + * 7 MHz bandwidth. + */ + public static final int BANDWIDTH_7MHZ = Constants.FrontendIsdbtBandwidth.BANDWIDTH_7MHZ; + /** + * 6 MHz bandwidth. + */ + public static final int BANDWIDTH_6MHZ = Constants.FrontendIsdbtBandwidth.BANDWIDTH_6MHZ; + + private final int mModulation; + private final int mBandwidth; + private final int mCoderate; + private final int mGuardInterval; + private final int mServiceAreaId; + + private IsdbtFrontendSettings(int frequency, int modulation, int bandwidth, int coderate, + int guardInterval, int serviceAreaId) { super(frequency); + mModulation = modulation; + mBandwidth = bandwidth; + mCoderate = coderate; + mGuardInterval = guardInterval; + mServiceAreaId = serviceAreaId; + } + + /** + * Gets Modulation. + */ + @Modulation + public int getModulation() { + return mModulation; + } + /** + * Gets Bandwidth. + */ + @Bandwidth + public int getBandwidth() { + return mBandwidth; + } + /** + * Gets Code rate. + */ + @DvbtFrontendSettings.Coderate + public int getCoderate() { + return mCoderate; + } + /** + * Gets Guard Interval. + */ + @DvbtFrontendSettings.GuardInterval + public int getGuardInterval() { + return mGuardInterval; + } + /** + * Gets Service Area ID. + */ + public int getServiceAreaId() { + return mServiceAreaId; + } + + /** + * Creates a builder for {@link IsdbtFrontendSettings}. + * + * @param context the context of the caller. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @NonNull + public static Builder builder(@NonNull Context context) { + TunerUtils.checkTunerPermission(context); + return new Builder(); + } + + /** + * Builder for {@link IsdbtFrontendSettings}. + */ + public static class Builder extends FrontendSettings.Builder<Builder> { + private int mModulation; + private int mBandwidth; + private int mCoderate; + private int mGuardInterval; + private int mServiceAreaId; + + private Builder() { + } + + /** + * Sets Modulation. + */ + @NonNull + public Builder setModulation(@Modulation int modulation) { + mModulation = modulation; + return this; + } + /** + * Sets Bandwidth. + */ + @NonNull + public Builder setBandwidth(@Bandwidth int bandwidth) { + mBandwidth = bandwidth; + return this; + } + /** + * Sets Code rate. + */ + @NonNull + public Builder setCoderate(@DvbtFrontendSettings.Coderate int coderate) { + mCoderate = coderate; + return this; + } + /** + * Sets Guard Interval. + */ + @NonNull + public Builder setGuardInterval(@DvbtFrontendSettings.GuardInterval int guardInterval) { + mGuardInterval = guardInterval; + return this; + } + /** + * Sets Service Area ID. + */ + @NonNull + public Builder setServiceAreaId(int serviceAreaId) { + mServiceAreaId = serviceAreaId; + return this; + } + + /** + * Builds a {@link IsdbtFrontendSettings} object. + */ + @NonNull + public IsdbtFrontendSettings build() { + return new IsdbtFrontendSettings( + mFrequency, mModulation, mBandwidth, mCoderate, mGuardInterval, mServiceAreaId); + } + + @Override + Builder self() { + return this; + } } @Override diff --git a/media/java/android/media/tv/tuner/frontend/ScanCallback.java b/media/java/android/media/tv/tuner/frontend/ScanCallback.java new file mode 100644 index 000000000000..5e7d2189706c --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/ScanCallback.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv.tuner.frontend; + +/** + * Scan callback. + * + * @hide + */ +public interface ScanCallback { + /** Scan locked the signal. */ + void onLocked(boolean isLocked); + + /** Scan stopped. */ + void onEnd(boolean isEnd); + + /** scan progress percent (0..100) */ + void onProgress(int percent); + + /** Signal frequency in Hertz */ + void onFrequencyReport(int frequency); + + /** Symbols per second */ + void onSymbolRate(int rate); + + /** Locked Plp Ids for DVBT2 frontend. */ + void onPlpIds(int[] plpIds); + + /** Locked group Ids for DVBT2 frontend. */ + void onGroupIds(int[] groupIds); + + /** Stream Ids. */ + void onInputStreamIds(int[] inputStreamIds); + + /** Locked signal standard. */ + void onDvbsStandard(@DvbsFrontendSettings.Standard int dvbsStandandard); + + /** Locked signal standard. */ + void onDvbtStandard(@DvbtFrontendSettings.Standard int dvbtStandard); + + /** PLP status in a tuned frequency band for ATSC3 frontend. */ + void onAtsc3PlpInfos(Atsc3PlpInfo[] atsc3PlpInfos); + + /** PLP information for ATSC3. */ + class Atsc3PlpInfo { + private final int mPlpId; + private final boolean mLlsFlag; + + private Atsc3PlpInfo(int plpId, boolean llsFlag) { + mPlpId = plpId; + mLlsFlag = llsFlag; + } + + /** Gets PLP IDs. */ + public int getPlpId() { + return mPlpId; + } + + /** Gets LLS flag. */ + public boolean getLlsFlag() { + return mLlsFlag; + } + } +} diff --git a/media/java/android/media/tv/tuner/frontend/ScanMessage.java b/media/java/android/media/tv/tuner/frontend/ScanMessage.java deleted file mode 100644 index dd687dd2959c..000000000000 --- a/media/java/android/media/tv/tuner/frontend/ScanMessage.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.media.tv.tuner.frontend; - -import android.annotation.IntDef; -import android.hardware.tv.tuner.V1_0.Constants; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Message from frontend during scan operations. - * - * @hide - */ -public class ScanMessage { - - /** @hide */ - @IntDef({ - LOCKED, - END, - PROGRESS_PERCENT, - FREQUENCY, - SYMBOL_RATE, - PLP_IDS, - GROUP_IDS, - INPUT_STREAM_IDS, - STANDARD, - ATSC3_PLP_INFO - }) - @Retention(RetentionPolicy.SOURCE) - public @interface Type {} - /** @hide */ - public static final int LOCKED = Constants.FrontendScanMessageType.LOCKED; - /** @hide */ - public static final int END = Constants.FrontendScanMessageType.END; - /** @hide */ - public static final int PROGRESS_PERCENT = Constants.FrontendScanMessageType.PROGRESS_PERCENT; - /** @hide */ - public static final int FREQUENCY = Constants.FrontendScanMessageType.FREQUENCY; - /** @hide */ - public static final int SYMBOL_RATE = Constants.FrontendScanMessageType.SYMBOL_RATE; - /** @hide */ - public static final int PLP_IDS = Constants.FrontendScanMessageType.PLP_IDS; - /** @hide */ - public static final int GROUP_IDS = Constants.FrontendScanMessageType.GROUP_IDS; - /** @hide */ - public static final int INPUT_STREAM_IDS = Constants.FrontendScanMessageType.INPUT_STREAM_IDS; - /** @hide */ - public static final int STANDARD = Constants.FrontendScanMessageType.STANDARD; - /** @hide */ - public static final int ATSC3_PLP_INFO = Constants.FrontendScanMessageType.ATSC3_PLP_INFO; - - private final int mType; - private final Object mValue; - - private ScanMessage(int type, Object value) { - mType = type; - mValue = value; - } - - /** Gets scan message type. */ - @Type - public int getMessageType() { - return mType; - } - /** Message indicates whether frontend is locked or not. */ - public boolean getIsLocked() { - if (mType != LOCKED) { - throw new IllegalStateException(); - } - return (Boolean) mValue; - } - /** Message indicates whether the scan has reached the end or not. */ - public boolean getIsEnd() { - if (mType != END) { - throw new IllegalStateException(); - } - return (Boolean) mValue; - } - /** Progress message in percent. */ - public int getProgressPercent() { - if (mType != PROGRESS_PERCENT) { - throw new IllegalStateException(); - } - return (Integer) mValue; - } - /** Gets frequency. */ - public int getFrequency() { - if (mType != FREQUENCY) { - throw new IllegalStateException(); - } - return (Integer) mValue; - } - /** Gets symbol rate. */ - public int getSymbolRate() { - if (mType != SYMBOL_RATE) { - throw new IllegalStateException(); - } - return (Integer) mValue; - } - /** Gets PLP IDs. */ - public int[] getPlpIds() { - if (mType != PLP_IDS) { - throw new IllegalStateException(); - } - return (int[]) mValue; - } - /** Gets group IDs. */ - public int[] getGroupIds() { - if (mType != GROUP_IDS) { - throw new IllegalStateException(); - } - return (int[]) mValue; - } - /** Gets Input stream IDs. */ - public int[] getInputStreamIds() { - if (mType != INPUT_STREAM_IDS) { - throw new IllegalStateException(); - } - return (int[]) mValue; - } - /** Gets the DVB-T or DVB-S standard. */ - public int getStandard() { - if (mType != STANDARD) { - throw new IllegalStateException(); - } - return (int) mValue; - } - - /** Gets PLP information for ATSC3. */ - public Atsc3PlpInfo[] getAtsc3PlpInfos() { - if (mType != ATSC3_PLP_INFO) { - throw new IllegalStateException(); - } - return (Atsc3PlpInfo[]) mValue; - } - - /** PLP information for ATSC3. */ - public static class Atsc3PlpInfo { - private final int mPlpId; - private final boolean mLlsFlag; - - private Atsc3PlpInfo(int plpId, boolean llsFlag) { - mPlpId = plpId; - mLlsFlag = llsFlag; - } - - /** Gets PLP IDs. */ - public int getPlpId() { - return mPlpId; - } - /** Gets LLS flag. */ - public boolean getLlsFlag() { - return mLlsFlag; - } - } -} diff --git a/media/jni/Android.bp b/media/jni/Android.bp index 536a061190d7..aeacd8f63cb0 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -19,6 +19,7 @@ cc_library_shared { "android_media_MediaProfiles.cpp", "android_media_MediaRecorder.cpp", "android_media_MediaSync.cpp", + "android_media_MediaTranscodeManager.cpp", "android_media_ResampleInputStream.cpp", "android_media_Streams.cpp", "android_media_SyncParams.cpp", diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index 963b650292e4..5cb42a9a96cc 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -1453,6 +1453,7 @@ extern int register_android_media_MediaProfiles(JNIEnv *env); extern int register_android_mtp_MtpDatabase(JNIEnv *env); extern int register_android_mtp_MtpDevice(JNIEnv *env); extern int register_android_mtp_MtpServer(JNIEnv *env); +extern int register_android_media_MediaTranscodeManager(JNIEnv *env); jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) { @@ -1565,6 +1566,11 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) goto bail; } + if (register_android_media_MediaTranscodeManager(env) < 0) { + ALOGE("ERROR: MediaTranscodeManager native registration failed"); + goto bail; + } + /* success -- return valid version number */ result = JNI_VERSION_1_4; diff --git a/media/jni/android_media_MediaTranscodeManager.cpp b/media/jni/android_media_MediaTranscodeManager.cpp new file mode 100644 index 000000000000..0b4048c1170c --- /dev/null +++ b/media/jni/android_media_MediaTranscodeManager.cpp @@ -0,0 +1,102 @@ +/* + * Copyright 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +//#define LOG_NDEBUG 0 +#define LOG_TAG "MediaTranscodeManager_JNI" + +#include "android_runtime/AndroidRuntime.h" +#include "jni.h" + +#include <nativehelper/JNIHelp.h> +#include <utils/Log.h> + +namespace { + +// NOTE: Keep these enums in sync with their equivalents in MediaTranscodeManager.java. +enum { + ID_INVALID = -1 +}; + +enum { + EVENT_JOB_STARTED = 1, + EVENT_JOB_PROGRESSED = 2, + EVENT_JOB_FINISHED = 3, +}; + +enum { + RESULT_NONE = 1, + RESULT_SUCCESS = 2, + RESULT_ERROR = 3, + RESULT_CANCELED = 4, +}; + +struct { + jmethodID postEventFromNative; +} gMediaTranscodeManagerClassInfo; + +using namespace android; + +void android_media_MediaTranscodeManager_native_init(JNIEnv *env, jclass clazz) { + ALOGV("android_media_MediaTranscodeManager_native_init"); + + gMediaTranscodeManagerClassInfo.postEventFromNative = env->GetMethodID( + clazz, "postEventFromNative", "(IJI)V"); + LOG_ALWAYS_FATAL_IF(gMediaTranscodeManagerClassInfo.postEventFromNative == NULL, + "can't find android/media/MediaTranscodeManager.postEventFromNative"); +} + +jlong android_media_MediaTranscodeManager_requestUniqueJobID( + JNIEnv *env __unused, jobject thiz __unused) { + ALOGV("android_media_MediaTranscodeManager_reserveUniqueJobID"); + static std::atomic_int32_t sJobIDCounter{0}; + jlong id = (jlong)++sJobIDCounter; + return id; +} + +jboolean android_media_MediaTranscodeManager_enqueueTranscodingRequest( + JNIEnv *env, jobject thiz, jlong id, jobject request, jobject context __unused) { + ALOGV("android_media_MediaTranscodeManager_enqueueTranscodingRequest"); + if (!request) { + return ID_INVALID; + } + + env->CallVoidMethod(thiz, gMediaTranscodeManagerClassInfo.postEventFromNative, + EVENT_JOB_FINISHED, id, RESULT_ERROR); + return true; +} + +void android_media_MediaTranscodeManager_cancelTranscodingRequest( + JNIEnv *env __unused, jobject thiz __unused, jlong jobID __unused) { + ALOGV("android_media_MediaTranscodeManager_cancelTranscodingRequest"); +} + +const JNINativeMethod gMethods[] = { + { "native_init", "()V", + (void *)android_media_MediaTranscodeManager_native_init }, + { "native_requestUniqueJobID", "()J", + (void *)android_media_MediaTranscodeManager_requestUniqueJobID }, + { "native_enqueueTranscodingRequest", + "(JLandroid/media/MediaTranscodeManager$TranscodingRequest;Landroid/content/Context;)Z", + (void *)android_media_MediaTranscodeManager_enqueueTranscodingRequest }, + { "native_cancelTranscodingRequest", "(J)V", + (void *)android_media_MediaTranscodeManager_cancelTranscodingRequest }, +}; + +} // namespace anonymous + +int register_android_media_MediaTranscodeManager(JNIEnv *env) { + return AndroidRuntime::registerNativeMethods(env, + "android/media/MediaTranscodeManager", gMethods, NELEM(gMethods)); +} diff --git a/media/tests/MediaFrameworkTest/Android.bp b/media/tests/MediaFrameworkTest/Android.bp index f0fbc509cc9d..ecbe2b3eb8b7 100644 --- a/media/tests/MediaFrameworkTest/Android.bp +++ b/media/tests/MediaFrameworkTest/Android.bp @@ -7,6 +7,7 @@ android_test { ], static_libs: [ "mockito-target-minus-junit4", + "androidx.test.ext.junit", "androidx.test.rules", "android-ex-camera2", ], diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediatranscodemanager/MediaTranscodeManagerTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediatranscodemanager/MediaTranscodeManagerTest.java new file mode 100644 index 000000000000..eeda50e5c095 --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediatranscodemanager/MediaTranscodeManagerTest.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.mediaframeworktest.functional.mediatranscodemanager; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import android.media.MediaTranscodeManager; +import android.util.Log; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +@RunWith(AndroidJUnit4.class) +public class MediaTranscodeManagerTest { + private static final String TAG = "MediaTranscodeManagerTest"; + + /** The time to wait for the transcode operation to complete before failing the test. */ + private static final int TRANSCODE_TIMEOUT_SECONDS = 2; + + @Test + public void testMediaTranscodeManager() throws InterruptedException { + Log.d(TAG, "Starting: testMediaTranscodeManager"); + + Semaphore transcodeCompleteSemaphore = new Semaphore(0); + MediaTranscodeManager.TranscodingRequest request = + new MediaTranscodeManager.TranscodingRequest.Builder().build(); + Executor listenerExecutor = Executors.newSingleThreadExecutor(); + + MediaTranscodeManager mediaTranscodeManager = + MediaTranscodeManager.getInstance(ApplicationProvider.getApplicationContext()); + assertNotNull(mediaTranscodeManager); + + MediaTranscodeManager.TranscodingJob job; + job = mediaTranscodeManager.enqueueTranscodingRequest(request, listenerExecutor, + transcodingJob -> { + Log.d(TAG, "Transcoding completed with result: " + transcodingJob.getResult()); + transcodeCompleteSemaphore.release(); + }); + assertNotNull(job); + + job.setOnProgressChangedListener( + listenerExecutor, progress -> Log.d(TAG, "Progress: " + progress)); + + if (job != null) { + Log.d(TAG, "testMediaTranscodeManager - Waiting for transcode to complete."); + boolean finishedOnTime = transcodeCompleteSemaphore.tryAcquire( + TRANSCODE_TIMEOUT_SECONDS, TimeUnit.SECONDS); + assertTrue("Transcode failed to complete in time.", finishedOnTime); + } + } +} diff --git a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java index 221783b1c97d..cc2d1b193970 100644 --- a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java +++ b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java @@ -167,12 +167,11 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService } @Override - public void onCreateSession(String packageName, String routeId, String routeFeature, - long requestId) { + public void onCreateSession(String packageName, String routeId, long requestId) { MediaRoute2Info route = mRoutes.get(routeId); if (route == null || TextUtils.equals(ROUTE_ID3_SESSION_CREATION_FAILED, routeId)) { // Tell the router that session cannot be created by passing null as sessionInfo. - notifySessionCreated(/* sessionInfo= */ null, requestId); + notifySessionCreationFailed(requestId); return; } maybeDeselectRoute(routeId); @@ -185,8 +184,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService .build()); mRouteIdToSessionId.put(routeId, sessionId); - RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder( - sessionId, packageName, routeFeature) + RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(sessionId, packageName) .addSelectedRoute(routeId) .addSelectableRoute(ROUTE_ID4_TO_SELECT_AND_DESELECT) .addTransferrableRoute(ROUTE_ID5_TO_TRANSFER_TO) @@ -196,8 +194,13 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService } @Override - public void onDestroySession(String sessionId, RoutingSessionInfo lastSessionInfo) { - for (String routeId : lastSessionInfo.getSelectedRoutes()) { + public void onReleaseSession(String sessionId) { + RoutingSessionInfo sessionInfo = getSessionInfo(sessionId); + if (sessionInfo == null) { + return; + } + + for (String routeId : sessionInfo.getSelectedRoutes()) { mRouteIdToSessionId.remove(routeId); MediaRoute2Info route = mRoutes.get(routeId); if (route != null) { @@ -206,6 +209,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService .build()); } } + notifySessionReleased(sessionId); } @Override @@ -227,8 +231,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService .removeSelectableRoute(routeId) .addDeselectableRoute(routeId) .build(); - updateSessionInfo(newSessionInfo); - notifySessionInfoChanged(newSessionInfo); + notifySessionUpdated(newSessionInfo); } @Override @@ -247,7 +250,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService .build()); if (sessionInfo.getSelectedRoutes().size() == 1) { - releaseSession(sessionId); + notifySessionReleased(sessionId); return; } @@ -256,8 +259,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService .addSelectableRoute(routeId) .removeDeselectableRoute(routeId) .build(); - updateSessionInfo(newSessionInfo); - notifySessionInfoChanged(newSessionInfo); + notifySessionUpdated(newSessionInfo); } @Override @@ -269,8 +271,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService .removeDeselectableRoute(routeId) .removeTransferrableRoute(routeId) .build(); - updateSessionInfo(newSessionInfo); - notifySessionInfoChanged(newSessionInfo); + notifySessionUpdated(newSessionInfo); } void maybeDeselectRoute(String routeId) { diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRoute2InfoTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRoute2InfoTest.java new file mode 100644 index 000000000000..c46966fdc6d8 --- /dev/null +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRoute2InfoTest.java @@ -0,0 +1,363 @@ +/* + * 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 com.android.mediaroutertest; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.testng.Assert.assertThrows; + +import android.media.MediaRoute2Info; +import android.net.Uri; +import android.os.Bundle; +import android.os.Parcel; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tests {@link MediaRoute2Info} and its {@link MediaRoute2Info.Builder builder}. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class MediaRoute2InfoTest { + + public static final String TEST_ID = "test_id"; + public static final String TEST_NAME = "test_name"; + public static final String TEST_ROUTE_TYPE_0 = "test_route_type_0"; + public static final String TEST_ROUTE_TYPE_1 = "test_route_type_1"; + public static final int TEST_DEVICE_TYPE = MediaRoute2Info.DEVICE_TYPE_REMOTE_SPEAKER; + public static final Uri TEST_ICON_URI = Uri.parse("https://developer.android.com"); + public static final String TEST_DESCRIPTION = "test_description"; + public static final int TEST_CONNECTION_STATE = MediaRoute2Info.CONNECTION_STATE_CONNECTING; + public static final String TEST_CLIENT_PACKAGE_NAME = "com.test.client.package.name"; + public static final int TEST_VOLUME_HANDLING = MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE; + public static final int TEST_VOLUME_MAX = 100; + public static final int TEST_VOLUME = 65; + + public static final String TEST_KEY = "test_key"; + public static final String TEST_VALUE = "test_value"; + + @Test + public void testBuilderConstructorWithInvalidValues() { + final String nullId = null; + final String nullName = null; + final String emptyId = ""; + final String emptyName = ""; + final String validId = "valid_id"; + final String validName = "valid_name"; + + // ID is invalid + assertThrows(IllegalArgumentException.class, + () -> new MediaRoute2Info.Builder(nullId, validName)); + assertThrows(IllegalArgumentException.class, + () -> new MediaRoute2Info.Builder(emptyId, validName)); + + // name is invalid + assertThrows(IllegalArgumentException.class, + () -> new MediaRoute2Info.Builder(validId, nullName)); + assertThrows(IllegalArgumentException.class, + () -> new MediaRoute2Info.Builder(validId, emptyName)); + + // Both are invalid + assertThrows(IllegalArgumentException.class, + () -> new MediaRoute2Info.Builder(nullId, nullName)); + assertThrows(IllegalArgumentException.class, + () -> new MediaRoute2Info.Builder(nullId, emptyName)); + assertThrows(IllegalArgumentException.class, + () -> new MediaRoute2Info.Builder(emptyId, nullName)); + assertThrows(IllegalArgumentException.class, + () -> new MediaRoute2Info.Builder(emptyId, emptyName)); + + + // Null RouteInfo (1-argument constructor) + final MediaRoute2Info nullRouteInfo = null; + assertThrows(NullPointerException.class, + () -> new MediaRoute2Info.Builder(nullRouteInfo)); + } + + @Test + public void testBuilderBuildWithEmptyRouteTypesShouldThrowIAE() { + MediaRoute2Info.Builder builder = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME); + assertThrows(IllegalArgumentException.class, () -> builder.build()); + } + + @Test + public void testBuilderAndGettersOfMediaRoute2Info() { + Bundle extras = new Bundle(); + extras.putString(TEST_KEY, TEST_VALUE); + + MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME) + .addFeature(TEST_ROUTE_TYPE_0) + .addFeature(TEST_ROUTE_TYPE_1) + .setDeviceType(TEST_DEVICE_TYPE) + .setIconUri(TEST_ICON_URI) + .setDescription(TEST_DESCRIPTION) + .setConnectionState(TEST_CONNECTION_STATE) + .setClientPackageName(TEST_CLIENT_PACKAGE_NAME) + .setVolumeHandling(TEST_VOLUME_HANDLING) + .setVolumeMax(TEST_VOLUME_MAX) + .setVolume(TEST_VOLUME) + .setExtras(extras) + .build(); + + assertEquals(TEST_ID, routeInfo.getId()); + assertEquals(TEST_NAME, routeInfo.getName()); + + assertEquals(2, routeInfo.getFeatures().size()); + assertEquals(TEST_ROUTE_TYPE_0, routeInfo.getFeatures().get(0)); + assertEquals(TEST_ROUTE_TYPE_1, routeInfo.getFeatures().get(1)); + + assertEquals(TEST_DEVICE_TYPE, routeInfo.getDeviceType()); + assertEquals(TEST_ICON_URI, routeInfo.getIconUri()); + assertEquals(TEST_DESCRIPTION, routeInfo.getDescription()); + assertEquals(TEST_CONNECTION_STATE, routeInfo.getConnectionState()); + assertEquals(TEST_CLIENT_PACKAGE_NAME, routeInfo.getClientPackageName()); + assertEquals(TEST_VOLUME_HANDLING, routeInfo.getVolumeHandling()); + assertEquals(TEST_VOLUME_MAX, routeInfo.getVolumeMax()); + assertEquals(TEST_VOLUME, routeInfo.getVolume()); + + Bundle extrasOut = routeInfo.getExtras(); + assertNotNull(extrasOut); + assertTrue(extrasOut.containsKey(TEST_KEY)); + assertEquals(TEST_VALUE, extrasOut.getString(TEST_KEY)); + } + + @Test + public void testBuilderSetExtrasWithNull() { + MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME) + .addFeature(TEST_ROUTE_TYPE_0) + .setExtras(null) + .build(); + + assertNull(routeInfo.getExtras()); + } + + @Test + public void testBuilderaddFeatures() { + List<String> routeTypes = new ArrayList<>(); + routeTypes.add(TEST_ROUTE_TYPE_0); + routeTypes.add(TEST_ROUTE_TYPE_1); + + MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME) + .addFeatures(routeTypes) + .build(); + + assertEquals(routeTypes, routeInfo.getFeatures()); + } + + @Test + public void testBuilderclearFeatures() { + MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME) + .addFeature(TEST_ROUTE_TYPE_0) + .addFeature(TEST_ROUTE_TYPE_1) + // clearFeatures should clear the route types. + .clearFeatures() + .addFeature(TEST_ROUTE_TYPE_1) + .build(); + + assertEquals(1, routeInfo.getFeatures().size()); + assertEquals(TEST_ROUTE_TYPE_1, routeInfo.getFeatures().get(0)); + } + + @Test + public void testhasAnyFeaturesReturnsFalse() { + MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME) + .addFeature(TEST_ROUTE_TYPE_0) + .addFeature(TEST_ROUTE_TYPE_1) + .build(); + + List<String> testRouteTypes = new ArrayList<>(); + testRouteTypes.add("non_matching_route_type_1"); + testRouteTypes.add("non_matching_route_type_2"); + testRouteTypes.add("non_matching_route_type_3"); + testRouteTypes.add("non_matching_route_type_4"); + + assertFalse(routeInfo.hasAnyFeatures(testRouteTypes)); + } + + @Test + public void testhasAnyFeaturesReturnsTrue() { + MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME) + .addFeature(TEST_ROUTE_TYPE_0) + .addFeature(TEST_ROUTE_TYPE_1) + .build(); + + List<String> testRouteTypes = new ArrayList<>(); + testRouteTypes.add("non_matching_route_type_1"); + testRouteTypes.add("non_matching_route_type_2"); + testRouteTypes.add("non_matching_route_type_3"); + testRouteTypes.add(TEST_ROUTE_TYPE_1); + + assertTrue(routeInfo.hasAnyFeatures(testRouteTypes)); + } + + @Test + public void testEqualsCreatedWithSameArguments() { + Bundle extras = new Bundle(); + extras.putString(TEST_KEY, TEST_VALUE); + + MediaRoute2Info routeInfo1 = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME) + .addFeature(TEST_ROUTE_TYPE_0) + .addFeature(TEST_ROUTE_TYPE_1) + .setDeviceType(TEST_DEVICE_TYPE) + .setIconUri(TEST_ICON_URI) + .setDescription(TEST_DESCRIPTION) + .setConnectionState(TEST_CONNECTION_STATE) + .setClientPackageName(TEST_CLIENT_PACKAGE_NAME) + .setVolumeHandling(TEST_VOLUME_HANDLING) + .setVolumeMax(TEST_VOLUME_MAX) + .setVolume(TEST_VOLUME) + .setExtras(extras) + .build(); + + MediaRoute2Info routeInfo2 = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME) + .addFeature(TEST_ROUTE_TYPE_0) + .addFeature(TEST_ROUTE_TYPE_1) + .setDeviceType(TEST_DEVICE_TYPE) + .setIconUri(TEST_ICON_URI) + .setDescription(TEST_DESCRIPTION) + .setConnectionState(TEST_CONNECTION_STATE) + .setClientPackageName(TEST_CLIENT_PACKAGE_NAME) + .setVolumeHandling(TEST_VOLUME_HANDLING) + .setVolumeMax(TEST_VOLUME_MAX) + .setVolume(TEST_VOLUME) + .setExtras(extras) + .build(); + + assertEquals(routeInfo1, routeInfo2); + assertEquals(routeInfo1.hashCode(), routeInfo2.hashCode()); + } + + @Test + public void testEqualsCreatedWithBuilderCopyConstructor() { + Bundle extras = new Bundle(); + extras.putString(TEST_KEY, TEST_VALUE); + + MediaRoute2Info routeInfo1 = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME) + .addFeature(TEST_ROUTE_TYPE_0) + .addFeature(TEST_ROUTE_TYPE_1) + .setDeviceType(TEST_DEVICE_TYPE) + .setIconUri(TEST_ICON_URI) + .setDescription(TEST_DESCRIPTION) + .setConnectionState(TEST_CONNECTION_STATE) + .setClientPackageName(TEST_CLIENT_PACKAGE_NAME) + .setVolumeHandling(TEST_VOLUME_HANDLING) + .setVolumeMax(TEST_VOLUME_MAX) + .setVolume(TEST_VOLUME) + .setExtras(extras) + .build(); + + MediaRoute2Info routeInfo2 = new MediaRoute2Info.Builder(routeInfo1).build(); + + assertEquals(routeInfo1, routeInfo2); + assertEquals(routeInfo1.hashCode(), routeInfo2.hashCode()); + } + + @Test + public void testEqualsReturnFalse() { + Bundle extras = new Bundle(); + extras.putString(TEST_KEY, TEST_VALUE); + + MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME) + .addFeature(TEST_ROUTE_TYPE_0) + .addFeature(TEST_ROUTE_TYPE_1) + .setDeviceType(TEST_DEVICE_TYPE) + .setIconUri(TEST_ICON_URI) + .setDescription(TEST_DESCRIPTION) + .setConnectionState(TEST_CONNECTION_STATE) + .setClientPackageName(TEST_CLIENT_PACKAGE_NAME) + .setVolumeHandling(TEST_VOLUME_HANDLING) + .setVolumeMax(TEST_VOLUME_MAX) + .setVolume(TEST_VOLUME) + .setExtras(extras) + .build(); + + // Now, we will use copy constructor + assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo) + .addFeature("randomRouteType") + .build()); + assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo) + .setDeviceType(TEST_DEVICE_TYPE + 1) + .build()); + assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo) + .setIconUri(Uri.parse("randomUri")) + .build()); + assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo) + .setDescription("randomDescription") + .build()); + assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo) + .setConnectionState(TEST_CONNECTION_STATE + 1) + .build()); + assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo) + .setClientPackageName("randomPackageName") + .build()); + assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo) + .setVolumeHandling(TEST_VOLUME_HANDLING + 1) + .build()); + assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo) + .setVolumeMax(TEST_VOLUME_MAX + 100) + .build()); + assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo) + .setVolume(TEST_VOLUME + 10) + .build()); + // Note: Extras will not affect the equals. + } + + @Test + public void testParcelingAndUnParceling() { + Bundle extras = new Bundle(); + extras.putString(TEST_KEY, TEST_VALUE); + + MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME) + .addFeature(TEST_ROUTE_TYPE_0) + .addFeature(TEST_ROUTE_TYPE_1) + .setDeviceType(TEST_DEVICE_TYPE) + .setIconUri(TEST_ICON_URI) + .setDescription(TEST_DESCRIPTION) + .setConnectionState(TEST_CONNECTION_STATE) + .setClientPackageName(TEST_CLIENT_PACKAGE_NAME) + .setVolumeHandling(TEST_VOLUME_HANDLING) + .setVolumeMax(TEST_VOLUME_MAX) + .setVolume(TEST_VOLUME) + .setExtras(extras) + .build(); + + Parcel parcel = Parcel.obtain(); + routeInfo.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + MediaRoute2Info routeInfoFromParcel = MediaRoute2Info.CREATOR.createFromParcel(parcel); + assertEquals(routeInfo, routeInfoFromParcel); + assertEquals(routeInfo.hashCode(), routeInfoFromParcel.hashCode()); + + // Check extras + Bundle extrasOut = routeInfoFromParcel.getExtras(); + assertNotNull(extrasOut); + assertTrue(extrasOut.containsKey(TEST_KEY)); + assertEquals(TEST_VALUE, extrasOut.getString(TEST_KEY)); + } +} diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java index 007229a4df2b..e782aae7c2d0 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java @@ -16,17 +16,13 @@ package com.android.mediaroutertest; -import static android.media.MediaRoute2Info.CONNECTION_STATE_CONNECTED; import static android.media.MediaRoute2Info.CONNECTION_STATE_CONNECTING; import static android.media.MediaRoute2Info.DEVICE_TYPE_REMOTE_SPEAKER; -import static android.media.MediaRoute2Info.DEVICE_TYPE_REMOTE_TV; -import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_FIXED; import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE; import static com.android.mediaroutertest.MediaRouterManagerTest.FEATURES_ALL; import static com.android.mediaroutertest.MediaRouterManagerTest.FEATURES_SPECIAL; import static com.android.mediaroutertest.MediaRouterManagerTest.FEATURE_SAMPLE; -import static com.android.mediaroutertest.MediaRouterManagerTest.FEATURE_SPECIAL; import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID1; import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID2; import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID3_SESSION_CREATION_FAILED; @@ -132,57 +128,6 @@ public class MediaRouter2Test { } @Test - public void testRouteInfoInequality() { - MediaRoute2Info route = new MediaRoute2Info.Builder("id", "name") - .setDescription("description") - .setClientPackageName("com.android.mediaroutertest") - .setConnectionState(CONNECTION_STATE_CONNECTING) - .setIconUri(new Uri.Builder().path("icon").build()) - .addFeature(FEATURE_SAMPLE) - .setVolume(5) - .setVolumeMax(20) - .setVolumeHandling(PLAYBACK_VOLUME_VARIABLE) - .setDeviceType(DEVICE_TYPE_REMOTE_SPEAKER) - .build(); - - MediaRoute2Info routeDescription = new MediaRoute2Info.Builder(route) - .setDescription("another description").build(); - assertNotEquals(route, routeDescription); - - MediaRoute2Info routeConnectionState = new MediaRoute2Info.Builder(route) - .setConnectionState(CONNECTION_STATE_CONNECTED).build(); - assertNotEquals(route, routeConnectionState); - - MediaRoute2Info routeIcon = new MediaRoute2Info.Builder(route) - .setIconUri(new Uri.Builder().path("new icon").build()).build(); - assertNotEquals(route, routeIcon); - - MediaRoute2Info routeClient = new MediaRoute2Info.Builder(route) - .setClientPackageName("another.client.package").build(); - assertNotEquals(route, routeClient); - - MediaRoute2Info routeType = new MediaRoute2Info.Builder(route) - .addFeature(FEATURE_SPECIAL).build(); - assertNotEquals(route, routeType); - - MediaRoute2Info routeVolume = new MediaRoute2Info.Builder(route) - .setVolume(10).build(); - assertNotEquals(route, routeVolume); - - MediaRoute2Info routeVolumeMax = new MediaRoute2Info.Builder(route) - .setVolumeMax(30).build(); - assertNotEquals(route, routeVolumeMax); - - MediaRoute2Info routeVolumeHandling = new MediaRoute2Info.Builder(route) - .setVolumeHandling(PLAYBACK_VOLUME_FIXED).build(); - assertNotEquals(route, routeVolumeHandling); - - MediaRoute2Info routeDeviceType = new MediaRoute2Info.Builder(route) - .setVolume(DEVICE_TYPE_REMOTE_TV).build(); - assertNotEquals(route, routeDeviceType); - } - - @Test public void testControlVolumeWithRouter() throws Exception { Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURES_ALL); @@ -227,19 +172,8 @@ public class MediaRouter2Test { } @Test - public void testRequestCreateSessionWithInvalidArguments() { - MediaRoute2Info route = new MediaRoute2Info.Builder("id", "name").build(); - String routeFeature = "routeFeature"; - - // Tests null route - assertThrows(NullPointerException.class, - () -> mRouter2.requestCreateSession(null, routeFeature)); - - // Tests null or empty route feature - assertThrows(IllegalArgumentException.class, - () -> mRouter2.requestCreateSession(route, null)); - assertThrows(IllegalArgumentException.class, - () -> mRouter2.requestCreateSession(route, "")); + public void testRequestCreateSessionWithNullRoute() { + assertThrows(NullPointerException.class, () -> mRouter2.requestCreateSession(null)); } @Test @@ -261,14 +195,12 @@ public class MediaRouter2Test { public void onSessionCreated(RoutingController controller) { assertNotNull(controller); assertTrue(createRouteMap(controller.getSelectedRoutes()).containsKey(ROUTE_ID1)); - assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller.getRouteFeature())); controllers.add(controller); successLatch.countDown(); } @Override - public void onSessionCreationFailed(MediaRoute2Info requestedRoute, - String requestedRouteFeature) { + public void onSessionCreationFailed(MediaRoute2Info requestedRoute) { failureLatch.countDown(); } }; @@ -279,7 +211,7 @@ public class MediaRouter2Test { try { mRouter2.registerSessionCallback(mExecutor, sessionCallback); - mRouter2.requestCreateSession(route, FEATURE_SAMPLE); + mRouter2.requestCreateSession(route); assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); // onSessionCreationFailed should not be called. @@ -313,10 +245,8 @@ public class MediaRouter2Test { } @Override - public void onSessionCreationFailed(MediaRoute2Info requestedRoute, - String requestedRouteFeature) { + public void onSessionCreationFailed(MediaRoute2Info requestedRoute) { assertEquals(route, requestedRoute); - assertTrue(TextUtils.equals(FEATURE_SAMPLE, requestedRouteFeature)); failureLatch.countDown(); } }; @@ -327,7 +257,7 @@ public class MediaRouter2Test { try { mRouter2.registerSessionCallback(mExecutor, sessionCallback); - mRouter2.requestCreateSession(route, FEATURE_SAMPLE); + mRouter2.requestCreateSession(route); assertTrue(failureLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); // onSessionCreated should not be called. @@ -357,8 +287,7 @@ public class MediaRouter2Test { } @Override - public void onSessionCreationFailed(MediaRoute2Info requestedRoute, - String requestedRouteFeature) { + public void onSessionCreationFailed(MediaRoute2Info requestedRoute) { failureLatch.countDown(); } }; @@ -375,8 +304,8 @@ public class MediaRouter2Test { try { mRouter2.registerSessionCallback(mExecutor, sessionCallback); - mRouter2.requestCreateSession(route1, FEATURE_SAMPLE); - mRouter2.requestCreateSession(route2, FEATURE_SAMPLE); + mRouter2.requestCreateSession(route1); + mRouter2.requestCreateSession(route2); assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); // onSessionCreationFailed should not be called. @@ -390,8 +319,6 @@ public class MediaRouter2Test { assertNotEquals(controller1.getSessionId(), controller2.getSessionId()); assertTrue(createRouteMap(controller1.getSelectedRoutes()).containsKey(ROUTE_ID1)); assertTrue(createRouteMap(controller2.getSelectedRoutes()).containsKey(ROUTE_ID2)); - assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller1.getRouteFeature())); - assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller2.getRouteFeature())); } finally { releaseControllers(createdControllers); @@ -422,8 +349,7 @@ public class MediaRouter2Test { } @Override - public void onSessionCreationFailed(MediaRoute2Info requestedRoute, - String requestedRouteFeature) { + public void onSessionCreationFailed(MediaRoute2Info requestedRoute) { failureLatch.countDown(); } }; @@ -434,7 +360,7 @@ public class MediaRouter2Test { try { mRouter2.registerSessionCallback(mExecutor, sessionCallback); - mRouter2.requestCreateSession(route, FEATURE_SAMPLE); + mRouter2.requestCreateSession(route); // Unregisters session callback mRouter2.unregisterSessionCallback(sessionCallback); @@ -470,7 +396,6 @@ public class MediaRouter2Test { public void onSessionCreated(RoutingController controller) { assertNotNull(controller); assertTrue(getRouteIds(controller.getSelectedRoutes()).contains(ROUTE_ID1)); - assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller.getRouteFeature())); controllers.add(controller); onSessionCreatedLatch.countDown(); } @@ -523,7 +448,7 @@ public class MediaRouter2Test { try { mRouter2.registerSessionCallback(mExecutor, sessionCallback); - mRouter2.requestCreateSession(routeToCreateSessionWith, FEATURE_SAMPLE); + mRouter2.requestCreateSession(routeToCreateSessionWith); assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); assertEquals(1, controllers.size()); @@ -568,7 +493,6 @@ public class MediaRouter2Test { public void onSessionCreated(RoutingController controller) { assertNotNull(controller); assertTrue(getRouteIds(controller.getSelectedRoutes()).contains(ROUTE_ID1)); - assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller.getRouteFeature())); controllers.add(controller); onSessionCreatedLatch.countDown(); } @@ -605,7 +529,7 @@ public class MediaRouter2Test { try { mRouter2.registerSessionCallback(mExecutor, sessionCallback); - mRouter2.requestCreateSession(routeToCreateSessionWith, FEATURE_SAMPLE); + mRouter2.requestCreateSession(routeToCreateSessionWith); assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); assertEquals(1, controllers.size()); @@ -647,7 +571,6 @@ public class MediaRouter2Test { public void onSessionCreated(RoutingController controller) { assertNotNull(controller); assertTrue(getRouteIds(controller.getSelectedRoutes()).contains(ROUTE_ID1)); - assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller.getRouteFeature())); controllers.add(controller); onSessionCreatedLatch.countDown(); } @@ -670,7 +593,7 @@ public class MediaRouter2Test { try { mRouter2.registerSessionCallback(mExecutor, sessionCallback); - mRouter2.requestCreateSession(routeToCreateSessionWith, FEATURE_SAMPLE); + mRouter2.requestCreateSession(routeToCreateSessionWith); assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); assertEquals(1, controllers.size()); diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java index ae15b913631b..83dd0c0fd301 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java @@ -20,7 +20,6 @@ import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_FIXED; import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -143,20 +142,6 @@ public class MediaRouterManagerTest { clearCallbacks(); } - //TODO: Move to a separate file - @Test - public void testMediaRoute2Info() { - MediaRoute2Info routeInfo1 = new MediaRoute2Info.Builder("id", "name") - .build(); - MediaRoute2Info routeInfo2 = new MediaRoute2Info.Builder(routeInfo1).build(); - - MediaRoute2Info routeInfo3 = new MediaRoute2Info.Builder(routeInfo1) - .setClientPackageName(mPackageName).build(); - - assertEquals(routeInfo1, routeInfo2); - assertNotEquals(routeInfo1, routeInfo3); - } - /** * Tests if routes are added correctly when a new callback is registered. */ diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java index 3f5973615e32..704dca0427ed 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java @@ -57,65 +57,32 @@ public class RoutingSessionInfoTest { public void testBuilderConstructorWithInvalidValues() { final String nullId = null; final String nullClientPackageName = null; - final String nullRouteFeature = null; final String emptyId = ""; // Note: An empty string as client package name is valid. - final String emptyRouteFeature = ""; final String validId = TEST_ID; final String validClientPackageName = TEST_CLIENT_PACKAGE_NAME; - final String validRouteFeature = TEST_ROUTE_FEATURE; // ID is invalid assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( - nullId, validClientPackageName, validRouteFeature)); + nullId, validClientPackageName)); assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( - emptyId, validClientPackageName, validRouteFeature)); + emptyId, validClientPackageName)); // client package name is invalid (null) assertThrows(NullPointerException.class, () -> new RoutingSessionInfo.Builder( - validId, nullClientPackageName, validRouteFeature)); + validId, nullClientPackageName)); - // route feature is invalid + // Both are invalid assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( - validId, validClientPackageName, nullRouteFeature)); + nullId, nullClientPackageName)); assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( - validId, validClientPackageName, emptyRouteFeature)); - - // Two arguments are invalid - (1) ID and clientPackageName - assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( - nullId, nullClientPackageName, validRouteFeature)); - assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( - emptyId, nullClientPackageName, validRouteFeature)); - - // Two arguments are invalid - (2) ID and routeFeature - assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( - nullId, validClientPackageName, nullRouteFeature)); - assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( - nullId, validClientPackageName, emptyRouteFeature)); - assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( - emptyId, validClientPackageName, nullRouteFeature)); - assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( - emptyId, validClientPackageName, emptyRouteFeature)); - - // Two arguments are invalid - (3) clientPackageName and routeFeature - // Note that this throws NullPointerException. - assertThrows(NullPointerException.class, () -> new RoutingSessionInfo.Builder( - validId, nullClientPackageName, nullRouteFeature)); - assertThrows(NullPointerException.class, () -> new RoutingSessionInfo.Builder( - validId, nullClientPackageName, emptyRouteFeature)); - - // All arguments are invalid - assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( - nullId, nullClientPackageName, nullRouteFeature)); - assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( - nullId, nullClientPackageName, emptyRouteFeature)); - assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( - emptyId, nullClientPackageName, nullRouteFeature)); - assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( - emptyId, nullClientPackageName, emptyRouteFeature)); + emptyId, nullClientPackageName)); + } + @Test + public void testBuilderCopyConstructorWithNull() { // Null RouteInfo (1-argument constructor) final RoutingSessionInfo nullRoutingSessionInfo = null; assertThrows(NullPointerException.class, @@ -127,13 +94,13 @@ public class RoutingSessionInfoTest { // An empty string for client package name is valid. (for unknown cases) // Creating builder with it should not throw any exception. RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder( - TEST_ID, "" /* clientPackageName*/, TEST_ROUTE_FEATURE); + TEST_ID, "" /* clientPackageName*/); } @Test public void testBuilderBuildWithEmptySelectedRoutesThrowsIAE() { RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder( - TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE); + TEST_ID, TEST_CLIENT_PACKAGE_NAME); // Note: Calling build() without adding any selected routes. assertThrows(IllegalArgumentException.class, () -> builder.build()); } @@ -141,7 +108,7 @@ public class RoutingSessionInfoTest { @Test public void testBuilderAddRouteMethodsWithIllegalArgumentsThrowsIAE() { RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder( - TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE); + TEST_ID, TEST_CLIENT_PACKAGE_NAME); final String nullRouteId = null; final String emptyRouteId = ""; @@ -168,7 +135,7 @@ public class RoutingSessionInfoTest { @Test public void testBuilderRemoveRouteMethodsWithIllegalArgumentsThrowsIAE() { RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder( - TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE); + TEST_ID, TEST_CLIENT_PACKAGE_NAME); final String nullRouteId = null; final String emptyRouteId = ""; @@ -198,7 +165,7 @@ public class RoutingSessionInfoTest { controlHints.putString(TEST_KEY, TEST_VALUE); RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder( - TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE) + TEST_ID, TEST_CLIENT_PACKAGE_NAME) .addSelectedRoute(TEST_ROUTE_ID_0) .addSelectedRoute(TEST_ROUTE_ID_1) .addSelectableRoute(TEST_ROUTE_ID_2) @@ -212,7 +179,6 @@ public class RoutingSessionInfoTest { assertEquals(TEST_ID, sessionInfo.getId()); assertEquals(TEST_CLIENT_PACKAGE_NAME, sessionInfo.getClientPackageName()); - assertEquals(TEST_ROUTE_FEATURE, sessionInfo.getRouteFeature()); assertEquals(2, sessionInfo.getSelectedRoutes().size()); assertEquals(TEST_ROUTE_ID_0, sessionInfo.getSelectedRoutes().get(0)); @@ -239,7 +205,7 @@ public class RoutingSessionInfoTest { @Test public void testBuilderAddRouteMethodsWithBuilderCopyConstructor() { RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder( - TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE) + TEST_ID, TEST_CLIENT_PACKAGE_NAME) .addSelectedRoute(TEST_ROUTE_ID_0) .addSelectableRoute(TEST_ROUTE_ID_2) .addDeselectableRoute(TEST_ROUTE_ID_4) @@ -273,7 +239,7 @@ public class RoutingSessionInfoTest { @Test public void testBuilderRemoveRouteMethods() { RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder( - TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE) + TEST_ID, TEST_CLIENT_PACKAGE_NAME) .addSelectedRoute(TEST_ROUTE_ID_0) .addSelectedRoute(TEST_ROUTE_ID_1) .removeSelectedRoute(TEST_ROUTE_ID_1) @@ -308,7 +274,7 @@ public class RoutingSessionInfoTest { @Test public void testBuilderRemoveRouteMethodsWithBuilderCopyConstructor() { RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder( - TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE) + TEST_ID, TEST_CLIENT_PACKAGE_NAME) .addSelectedRoute(TEST_ROUTE_ID_0) .addSelectedRoute(TEST_ROUTE_ID_1) .addSelectableRoute(TEST_ROUTE_ID_2) @@ -342,7 +308,7 @@ public class RoutingSessionInfoTest { @Test public void testBuilderClearRouteMethods() { RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder( - TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE) + TEST_ID, TEST_CLIENT_PACKAGE_NAME) .addSelectedRoute(TEST_ROUTE_ID_0) .addSelectedRoute(TEST_ROUTE_ID_1) .clearSelectedRoutes() @@ -374,7 +340,7 @@ public class RoutingSessionInfoTest { @Test public void testBuilderClearRouteMethodsWithBuilderCopyConstructor() { RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder( - TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE) + TEST_ID, TEST_CLIENT_PACKAGE_NAME) .addSelectedRoute(TEST_ROUTE_ID_0) .addSelectedRoute(TEST_ROUTE_ID_1) .addSelectableRoute(TEST_ROUTE_ID_2) @@ -408,7 +374,7 @@ public class RoutingSessionInfoTest { controlHints.putString(TEST_KEY, TEST_VALUE); RoutingSessionInfo sessionInfo1 = new RoutingSessionInfo.Builder( - TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE) + TEST_ID, TEST_CLIENT_PACKAGE_NAME) .addSelectedRoute(TEST_ROUTE_ID_0) .addSelectedRoute(TEST_ROUTE_ID_1) .addSelectableRoute(TEST_ROUTE_ID_2) @@ -421,7 +387,7 @@ public class RoutingSessionInfoTest { .build(); RoutingSessionInfo sessionInfo2 = new RoutingSessionInfo.Builder( - TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE) + TEST_ID, TEST_CLIENT_PACKAGE_NAME) .addSelectedRoute(TEST_ROUTE_ID_0) .addSelectedRoute(TEST_ROUTE_ID_1) .addSelectableRoute(TEST_ROUTE_ID_2) @@ -443,7 +409,7 @@ public class RoutingSessionInfoTest { controlHints.putString(TEST_KEY, TEST_VALUE); RoutingSessionInfo sessionInfo1 = new RoutingSessionInfo.Builder( - TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE) + TEST_ID, TEST_CLIENT_PACKAGE_NAME) .addSelectedRoute(TEST_ROUTE_ID_0) .addSelectedRoute(TEST_ROUTE_ID_1) .addSelectableRoute(TEST_ROUTE_ID_2) @@ -467,7 +433,7 @@ public class RoutingSessionInfoTest { controlHints.putString(TEST_KEY, TEST_VALUE); RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder( - TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE) + TEST_ID, TEST_CLIENT_PACKAGE_NAME) .addSelectedRoute(TEST_ROUTE_ID_0) .addSelectedRoute(TEST_ROUTE_ID_1) .addSelectableRoute(TEST_ROUTE_ID_2) @@ -530,7 +496,7 @@ public class RoutingSessionInfoTest { controlHints.putString(TEST_KEY, TEST_VALUE); RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder( - TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE) + TEST_ID, TEST_CLIENT_PACKAGE_NAME) .addSelectedRoute(TEST_ROUTE_ID_0) .addSelectedRoute(TEST_ROUTE_ID_1) .addSelectableRoute(TEST_ROUTE_ID_2) diff --git a/native/graphics/jni/bitmap.cpp b/native/graphics/jni/bitmap.cpp index 1aebeaf1e7e8..26c7f8d709e7 100644 --- a/native/graphics/jni/bitmap.cpp +++ b/native/graphics/jni/bitmap.cpp @@ -16,6 +16,7 @@ #include <android/bitmap.h> #include <android/graphics/bitmap.h> +#include <android/data_space.h> int AndroidBitmap_getInfo(JNIEnv* env, jobject jbitmap, AndroidBitmapInfo* info) { @@ -29,6 +30,15 @@ int AndroidBitmap_getInfo(JNIEnv* env, jobject jbitmap, return ANDROID_BITMAP_RESULT_SUCCESS; } +int32_t AndroidBitmap_getDataSpace(JNIEnv* env, jobject jbitmap) { + if (NULL == env || NULL == jbitmap) { + return ADATASPACE_UNKNOWN; // Or return a real error? + } + + android::graphics::Bitmap bitmap(env, jbitmap); + return bitmap.getDataSpace(); +} + int AndroidBitmap_lockPixels(JNIEnv* env, jobject jbitmap, void** addrPtr) { if (NULL == env || NULL == jbitmap) { return ANDROID_BITMAP_RESULT_BAD_PARAMETER; diff --git a/native/graphics/jni/libjnigraphics.map.txt b/native/graphics/jni/libjnigraphics.map.txt index bdd7f63b2d78..832770ffb97e 100644 --- a/native/graphics/jni/libjnigraphics.map.txt +++ b/native/graphics/jni/libjnigraphics.map.txt @@ -18,6 +18,7 @@ LIBJNIGRAPHICS { AImageDecoderHeaderInfo_isAnimated; AImageDecoderHeaderInfo_getAndroidBitmapFormat; AndroidBitmap_getInfo; + AndroidBitmap_getDataSpace; AndroidBitmap_lockPixels; AndroidBitmap_unlockPixels; local: diff --git a/packages/CarSystemUI/res/layout/sysui_primary_window.xml b/packages/CarSystemUI/res/layout/sysui_primary_window.xml index 309d9ef3ccfd..cc36e87eb480 100644 --- a/packages/CarSystemUI/res/layout/sysui_primary_window.xml +++ b/packages/CarSystemUI/res/layout/sysui_primary_window.xml @@ -15,9 +15,16 @@ ~ limitations under the License. --> +<!-- Fullscreen views in sysui should be listed here in increasing Z order. --> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:background="@android:color/transparent" android:layout_width="match_parent" android:layout_height="match_parent"> + + <ViewStub android:id="@+id/fullscreen_user_switcher_stub" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout="@layout/car_fullscreen_user_switcher"/> + </FrameLayout>
\ No newline at end of file diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java index 6ec385438e23..cfe1c702663e 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java @@ -22,7 +22,7 @@ import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; -import com.android.systemui.statusbar.notification.collection.NotificationRowBinder; +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.util.leak.LeakDetector; diff --git a/packages/CarSystemUI/src/com/android/systemui/car/SystemUIPrimaryWindowController.java b/packages/CarSystemUI/src/com/android/systemui/car/SystemUIPrimaryWindowController.java index e3e9ab75e3a2..c7e14d677b04 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/SystemUIPrimaryWindowController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/SystemUIPrimaryWindowController.java @@ -54,6 +54,7 @@ public class SystemUIPrimaryWindowController implements private ViewGroup mBaseLayout; private WindowManager.LayoutParams mLp; private WindowManager.LayoutParams mLpChanged; + private boolean mIsAttached = false; @Inject public SystemUIPrimaryWindowController( @@ -86,8 +87,17 @@ public class SystemUIPrimaryWindowController implements return mBaseLayout; } + /** Returns {@code true} if the window is already attached. */ + public boolean isAttached() { + return mIsAttached; + } + /** Attaches the window to the window manager. */ public void attach() { + if (mIsAttached) { + return; + } + mIsAttached = true; // Now that the status bar window encompasses the sliding panel and its // translucent backdrop, the entire thing is made TRANSLUCENT and is // hardware-accelerated. @@ -98,13 +108,14 @@ public class SystemUIPrimaryWindowController implements WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH - | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT); mLp.token = new Binder(); mLp.gravity = Gravity.TOP; mLp.setFitWindowInsetsTypes(/* types= */ 0); mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; - mLp.setTitle("NotificationShade"); + mLp.setTitle("SystemUIPrimaryWindow"); mLp.packageName = mContext.getPackageName(); mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; @@ -118,8 +129,11 @@ public class SystemUIPrimaryWindowController implements // TODO: Update this so that the windowing type gets the full height of the display // when we use MATCH_PARENT. mLpChanged.height = mDisplayHeight + mNavBarHeight; + mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; } else { mLpChanged.height = mStatusBarHeight; + // TODO: Allow touches to go through to the status bar to handle notification panel. + mLpChanged.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; } updateWindow(); } @@ -131,7 +145,9 @@ public class SystemUIPrimaryWindowController implements private void updateWindow() { if (mLp != null && mLp.copyFrom(mLpChanged) != 0) { - mWindowManager.updateViewLayout(mBaseLayout, mLp); + if (isAttached()) { + mWindowManager.updateViewLayout(mBaseLayout, mLp); + } } } } diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index 4521f5fb470e..18485dc283f0 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -67,6 +67,7 @@ import com.android.systemui.bubbles.BubbleController; import com.android.systemui.car.CarDeviceProvisionedController; import com.android.systemui.car.CarDeviceProvisionedListener; import com.android.systemui.car.CarServiceProvider; +import com.android.systemui.car.SystemUIPrimaryWindowController; import com.android.systemui.classifier.FalsingLog; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dagger.qualifiers.UiBackground; @@ -106,8 +107,8 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.VisualStabilityManager; -import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl; -import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline; +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; +import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.phone.AutoHideController; @@ -168,6 +169,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt // acceleration rate for the fling animation private static final float FLING_SPEED_UP_FACTOR = 0.6f; + private final UserSwitcherController mUserSwitcherController; private final ScrimController mScrimController; private final LockscreenLockIconController mLockscreenLockIconController; @@ -177,17 +179,16 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt private float mBackgroundAlphaDiff; private float mInitialBackgroundAlpha; - private final Lazy<FullscreenUserSwitcher> mFullscreenUserSwitcherLazy; - private FullscreenUserSwitcher mFullscreenUserSwitcher; - private CarBatteryController mCarBatteryController; private BatteryMeterView mBatteryMeterView; private Drawable mNotificationPanelBackground; private final Object mQueueLock = new Object(); + private final SystemUIPrimaryWindowController mSystemUIPrimaryWindowController; private final CarNavigationBarController mCarNavigationBarController; private final FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder; private final Lazy<PowerManagerHelper> mPowerManagerHelperLazy; + private final FullscreenUserSwitcher mFullscreenUserSwitcher; private final ShadeController mShadeController; private final CarServiceProvider mCarServiceProvider; private final CarDeviceProvisionedController mCarDeviceProvisionedController; @@ -268,7 +269,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt HeadsUpManagerPhone headsUpManagerPhone, DynamicPrivacyController dynamicPrivacyController, BypassHeadsUpNotifier bypassHeadsUpNotifier, - Lazy<NewNotifPipeline> newNotifPipeline, + Lazy<NotifPipelineInitializer> notifPipelineInitializer, FalsingManager falsingManager, BroadcastDispatcher broadcastDispatcher, RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler, @@ -338,7 +339,8 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt /* Car Settings injected components. */ CarServiceProvider carServiceProvider, Lazy<PowerManagerHelper> powerManagerHelperLazy, - Lazy<FullscreenUserSwitcher> fullscreenUserSwitcherLazy, + FullscreenUserSwitcher fullscreenUserSwitcher, + SystemUIPrimaryWindowController systemUIPrimaryWindowController, CarNavigationBarController carNavigationBarController, FlingAnimationUtils.Builder flingAnimationUtilsBuilder) { super( @@ -355,7 +357,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt headsUpManagerPhone, dynamicPrivacyController, bypassHeadsUpNotifier, - newNotifPipeline, + notifPipelineInitializer, falsingManager, broadcastDispatcher, remoteInputQuickSettingsDisabler, @@ -422,6 +424,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt userInfoControllerImpl, notificationRowBinder, dismissCallbackRegistry); + mUserSwitcherController = userSwitcherController; mScrimController = scrimController; mLockscreenLockIconController = lockscreenLockIconController; mCarDeviceProvisionedController = @@ -429,7 +432,8 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt mShadeController = shadeController; mCarServiceProvider = carServiceProvider; mPowerManagerHelperLazy = powerManagerHelperLazy; - mFullscreenUserSwitcherLazy = fullscreenUserSwitcherLazy; + mFullscreenUserSwitcher = fullscreenUserSwitcher; + mSystemUIPrimaryWindowController = systemUIPrimaryWindowController; mCarNavigationBarController = carNavigationBarController; mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder; } @@ -444,6 +448,13 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt mScreenLifecycle = Dependency.get(ScreenLifecycle.class); mScreenLifecycle.addObserver(mScreenObserver); + // TODO: Remove the setup of user switcher from Car Status Bar. + mSystemUIPrimaryWindowController.attach(); + mFullscreenUserSwitcher.setStatusBar(this); + mFullscreenUserSwitcher.setContainer( + mSystemUIPrimaryWindowController.getBaseLayout().findViewById( + R.id.fullscreen_user_switcher_stub)); + // Notification bar related setup. mInitialBackgroundAlpha = (float) mContext.getResources().getInteger( R.integer.config_initialNotificationBackgroundAlpha) / 100; @@ -510,16 +521,6 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt }); } - /** - * Allows for showing or hiding just the navigation bars. This is indented to be used when - * the full screen user selector is shown. - */ - void setNavBarVisibility(@View.Visibility int visibility) { - mCarNavigationBarController.setBottomWindowVisibility(visibility); - mCarNavigationBarController.setLeftWindowVisibility(visibility); - mCarNavigationBarController.setRightWindowVisibility(visibility); - } - @Override public boolean hideKeyguard() { boolean result = super.hideKeyguard(); @@ -924,9 +925,6 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt + " scroll " + mStackScroller.getScrollX() + "," + mStackScroller.getScrollY()); } - - pw.print(" mFullscreenUserSwitcher="); - pw.println(mFullscreenUserSwitcher); pw.print(" mCarBatteryController="); pw.println(mCarBatteryController); pw.print(" mBatteryMeterView="); @@ -972,14 +970,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt @Override protected void createUserSwitcher() { - UserSwitcherController userSwitcherController = - Dependency.get(UserSwitcherController.class); - if (userSwitcherController.useFullscreenUserSwitcher()) { - mFullscreenUserSwitcher = mFullscreenUserSwitcherLazy.get(); - mFullscreenUserSwitcher.setStatusBar(this); - mFullscreenUserSwitcher.setContainer( - mStatusBarWindow.findViewById(R.id.fullscreen_user_switcher_stub)); - } else { + if (!mUserSwitcherController.useFullscreenUserSwitcher()) { super.createUserSwitcher(); } } @@ -996,25 +987,12 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt super.onStateChanged(newState); if (newState != StatusBarState.FULLSCREEN_USER_SWITCHER) { - hideUserSwitcher(); + mFullscreenUserSwitcher.hide(); } else { dismissKeyguardWhenUserSwitcherNotDisplayed(); } } - /** Makes the full screen user switcher visible, if applicable. */ - public void showUserSwitcher() { - if (mFullscreenUserSwitcher != null && mState == StatusBarState.FULLSCREEN_USER_SWITCHER) { - mFullscreenUserSwitcher.show(); // Makes the switcher visible. - } - } - - private void hideUserSwitcher() { - if (mFullscreenUserSwitcher != null) { - mFullscreenUserSwitcher.hide(); - } - } - final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() { @Override public void onScreenTurnedOn() { @@ -1024,7 +1002,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt // We automatically dismiss keyguard unless user switcher is being shown on the keyguard. private void dismissKeyguardWhenUserSwitcherNotDisplayed() { - if (mFullscreenUserSwitcher == null) { + if (!mUserSwitcherController.useFullscreenUserSwitcher()) { return; // Not using the full screen user switcher. } diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarKeyguardViewManager.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarKeyguardViewManager.java index 0ad0992b68a4..2a2eb6976653 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarKeyguardViewManager.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarKeyguardViewManager.java @@ -24,6 +24,7 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.R; import com.android.systemui.dock.DockManager; +import com.android.systemui.navigationbar.car.CarNavigationBarController; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.phone.NavigationModeController; @@ -40,6 +41,8 @@ import javax.inject.Singleton; public class CarStatusBarKeyguardViewManager extends StatusBarKeyguardViewManager { protected boolean mShouldHideNavBar; + private final CarNavigationBarController mCarNavigationBarController; + private final FullscreenUserSwitcher mFullscreenUserSwitcher; @Inject public CarStatusBarKeyguardViewManager(Context context, @@ -52,13 +55,17 @@ public class CarStatusBarKeyguardViewManager extends StatusBarKeyguardViewManage DockManager dockManager, StatusBarWindowController statusBarWindowController, KeyguardStateController keyguardStateController, - NotificationMediaManager notificationMediaManager) { + NotificationMediaManager notificationMediaManager, + CarNavigationBarController carNavigationBarController, + FullscreenUserSwitcher fullscreenUserSwitcher) { super(context, callback, lockPatternUtils, sysuiStatusBarStateController, configurationController, keyguardUpdateMonitor, navigationModeController, dockManager, statusBarWindowController, keyguardStateController, notificationMediaManager); mShouldHideNavBar = context.getResources() .getBoolean(R.bool.config_hideNavWhenKeyguardBouncerShown); + mCarNavigationBarController = carNavigationBarController; + mFullscreenUserSwitcher = fullscreenUserSwitcher; } @Override @@ -66,8 +73,10 @@ public class CarStatusBarKeyguardViewManager extends StatusBarKeyguardViewManage if (!mShouldHideNavBar) { return; } - CarStatusBar statusBar = (CarStatusBar) mStatusBar; - statusBar.setNavBarVisibility(navBarVisible ? View.VISIBLE : View.GONE); + int visibility = navBarVisible ? View.VISIBLE : View.GONE; + mCarNavigationBarController.setBottomWindowVisibility(visibility); + mCarNavigationBarController.setLeftWindowVisibility(visibility); + mCarNavigationBarController.setRightWindowVisibility(visibility); } /** @@ -86,8 +95,7 @@ public class CarStatusBarKeyguardViewManager extends StatusBarKeyguardViewManage */ @Override public void onCancelClicked() { - CarStatusBar statusBar = (CarStatusBar) mStatusBar; - statusBar.showUserSwitcher(); + mFullscreenUserSwitcher.show(); } /** diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java index e5a091f94077..3abbe32df2da 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java @@ -31,6 +31,7 @@ import com.android.systemui.assist.AssistManager; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.car.CarServiceProvider; +import com.android.systemui.car.SystemUIPrimaryWindowController; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.keyguard.DismissCallbackRegistry; @@ -66,8 +67,8 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.VisualStabilityManager; -import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl; -import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline; +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; +import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.phone.AutoHideController; @@ -138,7 +139,7 @@ public class CarStatusBarModule { HeadsUpManagerPhone headsUpManagerPhone, DynamicPrivacyController dynamicPrivacyController, BypassHeadsUpNotifier bypassHeadsUpNotifier, - Lazy<NewNotifPipeline> newNotifPipeline, + Lazy<NotifPipelineInitializer> notifPipelineInitializer, FalsingManager falsingManager, BroadcastDispatcher broadcastDispatcher, RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler, @@ -207,7 +208,8 @@ public class CarStatusBarModule { DismissCallbackRegistry dismissCallbackRegistry, CarServiceProvider carServiceProvider, Lazy<PowerManagerHelper> powerManagerHelperLazy, - Lazy<FullscreenUserSwitcher> fullscreenUserSwitcherLazy, + FullscreenUserSwitcher fullscreenUserSwitcher, + SystemUIPrimaryWindowController systemUIPrimaryWindowController, CarNavigationBarController carNavigationBarController, FlingAnimationUtils.Builder flingAnimationUtilsBuilder) { return new CarStatusBar( @@ -224,7 +226,7 @@ public class CarStatusBarModule { headsUpManagerPhone, dynamicPrivacyController, bypassHeadsUpNotifier, - newNotifPipeline, + notifPipelineInitializer, falsingManager, broadcastDispatcher, remoteInputQuickSettingsDisabler, @@ -292,7 +294,8 @@ public class CarStatusBarModule { dismissCallbackRegistry, carServiceProvider, powerManagerHelperLazy, - fullscreenUserSwitcherLazy, + fullscreenUserSwitcher, + systemUIPrimaryWindowController, carNavigationBarController, flingAnimationUtilsBuilder); } diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java index f8fc3bbefb01..3cd66c232717 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java @@ -38,6 +38,7 @@ import androidx.recyclerview.widget.GridLayoutManager; import com.android.internal.widget.LockPatternUtils; import com.android.systemui.R; import com.android.systemui.car.CarServiceProvider; +import com.android.systemui.car.SystemUIPrimaryWindowController; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.statusbar.car.CarTrustAgentUnlockDialogHelper.OnHideListener; import com.android.systemui.statusbar.car.UserGridRecyclerView.UserRecord; @@ -56,9 +57,10 @@ public class FullscreenUserSwitcher { private final UserManager mUserManager; private final CarServiceProvider mCarServiceProvider; private final CarTrustAgentUnlockDialogHelper mUnlockDialogHelper; + private final SystemUIPrimaryWindowController mSystemUIPrimaryWindowController; + private CarStatusBar mCarStatusBar; private final int mShortAnimDuration; - private CarStatusBar mStatusBar; private View mParent; private UserGridRecyclerView mUserGridView; private CarTrustAgentEnrollmentManager mEnrollmentManager; @@ -81,23 +83,35 @@ public class FullscreenUserSwitcher { @Main Resources resources, UserManager userManager, CarServiceProvider carServiceProvider, - CarTrustAgentUnlockDialogHelper carTrustAgentUnlockDialogHelper) { + CarTrustAgentUnlockDialogHelper carTrustAgentUnlockDialogHelper, + SystemUIPrimaryWindowController systemUIPrimaryWindowController) { mContext = context; mResources = resources; mUserManager = userManager; mCarServiceProvider = carServiceProvider; mUnlockDialogHelper = carTrustAgentUnlockDialogHelper; + mSystemUIPrimaryWindowController = systemUIPrimaryWindowController; mShortAnimDuration = mResources.getInteger(android.R.integer.config_shortAnimTime); } - /** Sets the status bar which controls the keyguard. */ + /** Sets the status bar which gives an entry point to dismiss the keyguard. */ + // TODO: Remove this in favor of a keyguard controller. public void setStatusBar(CarStatusBar statusBar) { - mStatusBar = statusBar; + mCarStatusBar = statusBar; + } + + /** Returns {@code true} if the user switcher already has a parent view. */ + public boolean isAttached() { + return mParent != null; } /** Sets the {@link ViewStub} to show the user switcher. */ public void setContainer(ViewStub containerStub) { + if (isAttached()) { + return; + } + mParent = containerStub.inflate(); View container = mParent.findViewById(R.id.container); @@ -148,20 +162,31 @@ public class FullscreenUserSwitcher { * Makes user grid visible. */ public void show() { + if (!isAttached()) { + return; + } mParent.setVisibility(View.VISIBLE); + mSystemUIPrimaryWindowController.setWindowExpanded(true); } /** * Hides the user grid. */ public void hide() { + if (!isAttached()) { + return; + } mParent.setVisibility(View.INVISIBLE); + mSystemUIPrimaryWindowController.setWindowExpanded(false); } /** * @return {@code true} if user grid is visible, {@code false} otherwise. */ public boolean isVisible() { + if (!isAttached()) { + return false; + } return mParent.getVisibility() == View.VISIBLE; } @@ -196,7 +221,7 @@ public class FullscreenUserSwitcher { } if (mSelectedUser.mType == UserRecord.FOREGROUND_USER) { hide(); - mStatusBar.dismissKeyguard(); + mCarStatusBar.dismissKeyguard(); return; } // Switching is about to happen, since it takes time, fade out the switcher gradually. diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java index cdabeebe2819..8c756ecbaefc 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java @@ -43,6 +43,9 @@ import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowInsets; +import android.view.WindowManager; import android.widget.ImageView; import android.widget.TextView; @@ -53,7 +56,6 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.internal.util.UserIcons; import com.android.systemui.R; -import com.android.systemui.statusbar.phone.SystemUIDialog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -337,7 +339,7 @@ public class UserGridRecyclerView extends RecyclerView { .setPositiveButton(android.R.string.ok, null) .create(); // Sets window flags for the SysUI dialog - SystemUIDialog.applyFlags(maxUsersDialog); + applyCarSysUIDialogFlags(maxUsersDialog); maxUsersDialog.show(); } @@ -356,10 +358,19 @@ public class UserGridRecyclerView extends RecyclerView { .setOnCancelListener(this) .create(); // Sets window flags for the SysUI dialog - SystemUIDialog.applyFlags(addUserDialog); + applyCarSysUIDialogFlags(addUserDialog); addUserDialog.show(); } + private void applyCarSysUIDialogFlags(AlertDialog dialog) { + final Window window = dialog.getWindow(); + window.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL); + window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM + | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + window.setFitWindowInsetsTypes( + window.getFitWindowInsetsTypes() & ~WindowInsets.Type.statusBars()); + } + private void notifyUserSelected(UserRecord userRecord) { // Notify the listener which user was selected if (mUserSelectionListener != null) { diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java index 41a9b24afa03..50542818e0d7 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java @@ -49,7 +49,6 @@ import android.webkit.WebViewClient; import android.widget.ProgressBar; import android.widget.TextView; -import com.android.internal.telephony.PhoneConstants; import com.android.internal.util.ArrayUtils; import com.android.internal.util.TrafficStatsConstants; @@ -207,7 +206,7 @@ public class CaptivePortalLoginActivity extends Activity { if (TextUtils.isEmpty(url)) url = mCm.getCaptivePortalServerUrl(); final CarrierConfigManager configManager = getApplicationContext() .getSystemService(CarrierConfigManager.class); - final int subId = getIntent().getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, + final int subId = getIntent().getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, SubscriptionManager.getDefaultVoiceSubscriptionId()); final String[] portalURLs = configManager.getConfigForSubId(subId).getStringArray( CarrierConfigManager.KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY); diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java index 2697a1066ed2..cb062a63541e 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java @@ -30,8 +30,6 @@ import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; -import com.android.internal.telephony.PhoneConstants; - /** * This util class provides common logic for carrier actions */ @@ -103,7 +101,7 @@ public class CarrierActionUtils { } private static void onDisableAllMeteredApns(Intent intent, Context context) { - int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, + int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, SubscriptionManager.getDefaultVoiceSubscriptionId()); logd("onDisableAllMeteredApns subId: " + subId); final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class); @@ -111,7 +109,7 @@ public class CarrierActionUtils { } private static void onEnableAllMeteredApns(Intent intent, Context context) { - int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, + int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, SubscriptionManager.getDefaultVoiceSubscriptionId()); logd("onEnableAllMeteredApns subId: " + subId); final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class); @@ -135,7 +133,7 @@ public class CarrierActionUtils { } private static void onRegisterDefaultNetworkAvail(Intent intent, Context context) { - int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, + int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, SubscriptionManager.getDefaultVoiceSubscriptionId()); logd("onRegisterDefaultNetworkAvail subId: " + subId); final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class); @@ -143,7 +141,7 @@ public class CarrierActionUtils { } private static void onDeregisterDefaultNetworkAvail(Intent intent, Context context) { - int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, + int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, SubscriptionManager.getDefaultVoiceSubscriptionId()); logd("onDeregisterDefaultNetworkAvail subId: " + subId); final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class); @@ -151,7 +149,7 @@ public class CarrierActionUtils { } private static void onDisableRadio(Intent intent, Context context) { - int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, + int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, SubscriptionManager.getDefaultVoiceSubscriptionId()); logd("onDisableRadio subId: " + subId); final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class); @@ -159,7 +157,7 @@ public class CarrierActionUtils { } private static void onEnableRadio(Intent intent, Context context) { - int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, + int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, SubscriptionManager.getDefaultVoiceSubscriptionId()); logd("onEnableRadio subId: " + subId); final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class); @@ -202,7 +200,7 @@ public class CarrierActionUtils { } private static void onResetAllCarrierActions(Intent intent, Context context) { - int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, + int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, SubscriptionManager.getDefaultVoiceSubscriptionId()); logd("onResetAllCarrierActions subId: " + subId); final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class); diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java index 086a287fd243..6229434d1d86 100644 --- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java +++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java @@ -15,6 +15,7 @@ */ package com.android.carrierdefaultapp; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.times; @@ -26,11 +27,10 @@ import android.app.PendingIntent; import android.content.Intent; import android.os.PersistableBundle; import android.telephony.CarrierConfigManager; +import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.test.InstrumentationTestCase; -import com.android.internal.telephony.PhoneConstants; - import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -69,6 +69,7 @@ public class CarrierDefaultReceiverTest extends InstrumentationTestCase { mContext.injectSystemService(NotificationManager.class, mNotificationMgr); mContext.injectSystemService(TelephonyManager.class, mTelephonyMgr); mContext.injectSystemService(CarrierConfigManager.class, mCarrierConfigMgr); + doReturn(mTelephonyMgr).when(mTelephonyMgr).createForSubscriptionId(anyInt()); mReceiver = new CarrierDefaultBroadcastReceiver(); } @@ -88,7 +89,7 @@ public class CarrierDefaultReceiverTest extends InstrumentationTestCase { doReturn(b).when(mCarrierConfigMgr).getConfig(); Intent intent = new Intent(TelephonyManager.ACTION_CARRIER_SIGNAL_REDIRECTED); - intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId); + intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId); mReceiver.onReceive(mContext, intent); mContext.waitForMs(100); diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java index c2ce84023869..9ccb837cf613 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java @@ -56,6 +56,7 @@ import android.os.PowerManager; import android.os.RemoteException; import android.os.image.DynamicSystemClient; import android.os.image.DynamicSystemManager; +import android.text.TextUtils; import android.util.Log; import android.widget.Toast; @@ -74,6 +75,8 @@ public class DynamicSystemInstallationService extends Service // TODO (b/131866826): This is currently for test only. Will move this to System API. static final String KEY_ENABLE_WHEN_COMPLETED = "KEY_ENABLE_WHEN_COMPLETED"; + static final String KEY_DSU_SLOT = "KEY_DSU_SLOT"; + static final String DEFAULT_DSU_SLOT = "dsu"; /* * Intent actions @@ -244,10 +247,15 @@ public class DynamicSystemInstallationService extends Service long systemSize = intent.getLongExtra(DynamicSystemClient.KEY_SYSTEM_SIZE, 0); long userdataSize = intent.getLongExtra(DynamicSystemClient.KEY_USERDATA_SIZE, 0); mEnableWhenCompleted = intent.getBooleanExtra(KEY_ENABLE_WHEN_COMPLETED, false); + String dsuSlot = intent.getStringExtra(KEY_DSU_SLOT); + if (TextUtils.isEmpty(dsuSlot)) { + dsuSlot = DEFAULT_DSU_SLOT; + } // TODO: better constructor or builder - mInstallTask = new InstallationAsyncTask( - url, systemSize, userdataSize, this, mDynSystem, this); + mInstallTask = + new InstallationAsyncTask( + url, dsuSlot, systemSize, userdataSize, this, mDynSystem, this); mInstallTask.execute(); @@ -409,7 +417,9 @@ public class DynamicSystemInstallationService extends Service break; case STATUS_READY: - builder.setContentText(getString(R.string.notification_install_completed)); + String msgCompleted = getString(R.string.notification_install_completed); + builder.setContentText(msgCompleted) + .setStyle(new Notification.BigTextStyle().bigText(msgCompleted)); builder.addAction(new Notification.Action.Builder( null, getString(R.string.notification_action_discard), @@ -422,7 +432,9 @@ public class DynamicSystemInstallationService extends Service break; case STATUS_IN_USE: - builder.setContentText(getString(R.string.notification_dynsystem_in_use)); + String msgInUse = getString(R.string.notification_dynsystem_in_use); + builder.setContentText(msgInUse) + .setStyle(new Notification.BigTextStyle().bigText(msgInUse)); builder.addAction(new Notification.Action.Builder( null, getString(R.string.notification_action_uninstall), @@ -452,7 +464,49 @@ public class DynamicSystemInstallationService extends Service } private void postStatus(int status, int cause, Throwable detail) { - Log.d(TAG, "postStatus(): statusCode=" + status + ", causeCode=" + cause); + String statusString; + String causeString; + + switch (status) { + case STATUS_NOT_STARTED: + statusString = "NOT_STARTED"; + break; + case STATUS_IN_PROGRESS: + statusString = "IN_PROGRESS"; + break; + case STATUS_READY: + statusString = "READY"; + break; + case STATUS_IN_USE: + statusString = "IN_USE"; + break; + default: + statusString = "UNKNOWN"; + break; + } + + switch (cause) { + case CAUSE_INSTALL_COMPLETED: + causeString = "INSTALL_COMPLETED"; + break; + case CAUSE_INSTALL_CANCELLED: + causeString = "INSTALL_CANCELLED"; + break; + case CAUSE_ERROR_IO: + causeString = "ERROR_IO"; + break; + case CAUSE_ERROR_INVALID_URL: + causeString = "ERROR_INVALID_URL"; + break; + case CAUSE_ERROR_EXCEPTION: + causeString = "ERROR_EXCEPTION"; + break; + default: + causeString = "CAUSE_NOT_SPECIFIED"; + break; + } + + Log.d(TAG, "status=" + statusString + ", cause=" + causeString); boolean notifyOnNotificationBar = true; diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java index b206a1fccba4..9aea0e713179 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java @@ -89,10 +89,12 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog interface ProgressListener { void onProgressUpdate(Progress progress); + void onResult(int resultCode, Throwable detail); } private final String mUrl; + private final String mDsuSlot; private final long mSystemSize; private final long mUserdataSize; private final Context mContext; @@ -106,9 +108,16 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog private InputStream mStream; private ZipFile mZipFile; - InstallationAsyncTask(String url, long systemSize, long userdataSize, Context context, - DynamicSystemManager dynSystem, ProgressListener listener) { + InstallationAsyncTask( + String url, + String dsuSlot, + long systemSize, + long userdataSize, + Context context, + DynamicSystemManager dynSystem, + ProgressListener listener) { mUrl = url; + mDsuSlot = dsuSlot; mSystemSize = systemSize; mUserdataSize = userdataSize; mContext = context; @@ -126,14 +135,17 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog verifyAndPrepare(); - mDynSystem.startInstallation(); + mDynSystem.startInstallation(mDsuSlot); installUserdata(); if (isCancelled()) { mDynSystem.remove(); return null; } - + if (mUrl == null) { + mDynSystem.finishInstallation(); + return null; + } installImages(); if (isCancelled()) { mDynSystem.remove(); @@ -194,6 +206,9 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog } private void verifyAndPrepare() throws Exception { + if (mUrl == null) { + return; + } String extension = mUrl.substring(mUrl.lastIndexOf('.') + 1); if ("gz".equals(extension) || "gzip".equals(extension)) { diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java index 3b3933b7db10..e42ded74acd0 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java @@ -28,11 +28,9 @@ import android.os.image.DynamicSystemClient; import android.util.FeatureFlagUtils; import android.util.Log; - /** - * This Activity starts KeyguardManager and ask the user to confirm - * before any installation request. If the device is not protected by - * a password, it approves the request by default. + * This Activity starts KeyguardManager and ask the user to confirm before any installation request. + * If the device is not protected by a password, it approves the request by default. */ public class VerificationActivity extends Activity { @@ -88,11 +86,15 @@ public class VerificationActivity extends Activity { Uri url = callingIntent.getData(); Bundle extras = callingIntent.getExtras(); - sVerifiedUrl = url.toString(); + if (url != null) { + sVerifiedUrl = url.toString(); + } // start service Intent intent = new Intent(this, DynamicSystemInstallationService.class); - intent.setData(url); + if (url != null) { + intent.setData(url); + } intent.setAction(DynamicSystemClient.ACTION_START_INSTALL); intent.putExtras(extras); @@ -106,6 +108,7 @@ public class VerificationActivity extends Activity { } static boolean isVerified(String url) { + if (url == null) return true; return sVerifiedUrl != null && sVerifiedUrl.equals(url); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index 747ceb190a5d..50d3a5daeb84 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -262,13 +262,28 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> } } + /** + * Connect this device. + * + * @param connectAllProfiles {@code true} to connect all profile, {@code false} otherwise. + * + * @deprecated use {@link #connect()} instead. + */ + @Deprecated public void connect(boolean connectAllProfiles) { + connect(); + } + + /** + * Connect this device. + */ + public void connect() { if (!ensurePaired()) { return; } mConnectAttempted = SystemClock.elapsedRealtime(); - connectWithoutResettingTimer(connectAllProfiles); + connectAllEnabledProfiles(); } public long getHiSyncId() { @@ -289,10 +304,10 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> void onBondingDockConnect() { // Attempt to connect if UUIDs are available. Otherwise, // we will connect when the ACTION_UUID intent arrives. - connect(false); + connect(); } - private void connectWithoutResettingTimer(boolean connectAllProfiles) { + private void connectAllEnabledProfiles() { synchronized (mProfileLock) { // Try to initialize the profiles if they were not. if (mProfiles.isEmpty()) { @@ -307,36 +322,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> return; } - int preferredProfiles = 0; - for (LocalBluetoothProfile profile : mProfiles) { - if (connectAllProfiles ? profile.accessProfileEnabled() - : profile.isAutoConnectable()) { - if (profile.isPreferred(mDevice)) { - ++preferredProfiles; - connectInt(profile); - } - } - } - if (BluetoothUtils.D) Log.d(TAG, "Preferred profiles = " + preferredProfiles); - - if (preferredProfiles == 0) { - connectAutoConnectableProfiles(); - } - } - } - - private void connectAutoConnectableProfiles() { - if (!ensurePaired()) { - return; - } - - synchronized (mProfileLock) { - for (LocalBluetoothProfile profile : mProfiles) { - if (profile.isAutoConnectable()) { - profile.setPreferred(mDevice, true); - connectInt(profile); - } - } + mLocalAdapter.connectAllEnabledProfiles(mDevice); } } @@ -703,7 +689,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> */ if (!mProfiles.isEmpty() && ((mConnectAttempted + timeout) > SystemClock.elapsedRealtime())) { - connectWithoutResettingTimer(false); + connectAllEnabledProfiles(); } dispatchAttributesChanged(); @@ -722,7 +708,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> refresh(); if (bondState == BluetoothDevice.BOND_BONDED && mDevice.isBondingInitiatedLocally()) { - connect(false); + connect(); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java index 61e47f8f8dd8..6e7a429e6b7a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java +++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java @@ -87,8 +87,10 @@ public class VisibilityLoggerMixin implements LifecycleObserver, OnAttach { if (mMetricsFeature == null || mMetricsCategory == METRICS_CATEGORY_UNKNOWN) { return; } - final int elapse = (int) (SystemClock.elapsedRealtime() - mCreationTimestamp); - mMetricsFeature.action(METRICS_CATEGORY_UNKNOWN, action, mMetricsCategory, key, elapse); + if (mCreationTimestamp != 0L) { + final int elapse = (int) (SystemClock.elapsedRealtime() - mCreationTimestamp); + mMetricsFeature.action(METRICS_CATEGORY_UNKNOWN, action, mMetricsCategory, key, elapse); + } } /** diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java index 4ab9a9ac5915..b07fc2bee3f9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java @@ -61,6 +61,8 @@ public final class CategoryKey { "com.android.settings.category.ia.my_device_info"; public static final String CATEGORY_BATTERY_SAVER_SETTINGS = "com.android.settings.category.ia.battery_saver_settings"; + public static final String CATEGORY_SMART_BATTERY_SETTINGS = + "com.android.settings.category.ia.smart_battery_settings"; public static final Map<String, String> KEY_COMPAT_MAP; diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java index 70b56ed0b391..e85a102294d8 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java @@ -122,7 +122,7 @@ public class LocalMediaManager implements BluetoothCallback { final CachedBluetoothDevice cachedDevice = ((BluetoothMediaDevice) device).getCachedDevice(); if (!cachedDevice.isConnected() && !cachedDevice.isBusy()) { - cachedDevice.connect(true); + cachedDevice.connect(); return; } } diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java index 853c77efd4e9..05a6ce4578db 100644 --- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java @@ -40,7 +40,7 @@ public class DataUsageUtils { final NetworkTemplate mobileAll = NetworkTemplate.buildTemplateMobileAll( telephonyManager.createForSubscriptionId(subId).getSubscriberId()); - if (!subscriptionManager.isActiveSubId(subId)) { + if (!subscriptionManager.isActiveSubscriptionId(subId)) { Log.i(TAG, "Subscription is not active: " + subId); return mobileAll; } diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java index 81739e069e28..9d4c24e8faa4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java @@ -211,13 +211,6 @@ public class AccessPoint implements Comparable<AccessPoint> { private static final int EAP_WPA = 1; // WPA-EAP private static final int EAP_WPA2_WPA3 = 2; // RSN-EAP - /** - * The number of distinct wifi levels. - * - * <p>Must keep in sync with {@link R.array.wifi_signal} and {@link WifiManager#RSSI_LEVELS}. - */ - public static final int SIGNAL_LEVELS = 5; - public static final int UNREACHABLE_RSSI = Integer.MIN_VALUE; public static final String KEY_PREFIX_AP = "AP:"; @@ -453,9 +446,10 @@ public class AccessPoint implements Comparable<AccessPoint> { return other.getSpeed() - getSpeed(); } + WifiManager wifiManager = getWifiManager(); // Sort by signal strength, bucketed by level - int difference = WifiManager.calculateSignalLevel(other.mRssi, SIGNAL_LEVELS) - - WifiManager.calculateSignalLevel(mRssi, SIGNAL_LEVELS); + int difference = wifiManager.calculateSignalLevel(other.mRssi) + - wifiManager.calculateSignalLevel(mRssi); if (difference != 0) { return difference; } @@ -869,13 +863,14 @@ public class AccessPoint implements Comparable<AccessPoint> { } /** - * Returns the number of levels to show for a Wifi icon, from 0 to {@link #SIGNAL_LEVELS}-1. + * Returns the number of levels to show for a Wifi icon, from 0 to + * {@link WifiManager#getMaxSignalLevel()}. * - * <p>Use {@#isReachable()} to determine if an AccessPoint is in range, as this method will + * <p>Use {@link #isReachable()} to determine if an AccessPoint is in range, as this method will * always return at least 0. */ public int getLevel() { - return WifiManager.calculateSignalLevel(mRssi, SIGNAL_LEVELS); + return getWifiManager().calculateSignalLevel(mRssi); } public int getRssi() { diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java index 4a4f0e66cfe8..f21e466dd8ab 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java @@ -22,6 +22,7 @@ import android.net.NetworkInfo; import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; import android.os.Bundle; import android.os.Parcelable; @@ -126,13 +127,15 @@ public class TestAccessPointBuilder { @Keep public TestAccessPointBuilder setLevel(int level) { // Reversal of WifiManager.calculateSignalLevels + WifiManager wifiManager = mContext.getSystemService(WifiManager.class); + int maxSignalLevel = wifiManager.getMaxSignalLevel(); if (level == 0) { mRssi = MIN_RSSI; - } else if (level >= AccessPoint.SIGNAL_LEVELS) { + } else if (level > maxSignalLevel) { mRssi = MAX_RSSI; } else { float inputRange = MAX_RSSI - MIN_RSSI; - float outputRange = AccessPoint.SIGNAL_LEVELS - 1; + float outputRange = maxSignalLevel; mRssi = (int) (level * inputRange / outputRange + MIN_RSSI); } return this; diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java index 440315ffe37b..2d7d59cc0022 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java @@ -118,7 +118,7 @@ public class WifiEntryPreference extends Preference implements WifiEntry.WifiEnt notifyChanged(); } - setSummary(mWifiEntry.getSummary()); + setSummary(mWifiEntry.getSummary(false /* concise */)); mContentDescription = buildContentDescription(); } diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java index 8591116fce0f..3f55ceaad29a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java @@ -89,7 +89,7 @@ public class WifiStatusTracker extends ConnectivityManager.NetworkCallback { public void setListening(boolean listening) { if (listening) { mNetworkScoreManager.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, - mWifiNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK); + mWifiNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_CURRENT_NETWORK); mWifiNetworkScoreCache.registerListener(mCacheListener); mConnectivityManager.registerNetworkCallback( mNetworkRequest, mNetworkCallback, mHandler); diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java index ba6a8ea31987..ed4ff085aeac 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java @@ -361,7 +361,7 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro mNetworkScoreManager.registerNetworkScoreCache( NetworkKey.TYPE_WIFI, mScoreCache, - NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS); + NetworkScoreManager.SCORE_FILTER_SCAN_RESULTS); } private void requestScoresForNetworkKeys(Collection<NetworkKey> keys) { diff --git a/packages/SettingsLib/tests/integ/Android.bp b/packages/SettingsLib/tests/integ/Android.bp index 4600793729a2..2ccff1ecaf6c 100644 --- a/packages/SettingsLib/tests/integ/Android.bp +++ b/packages/SettingsLib/tests/integ/Android.bp @@ -14,7 +14,10 @@ android_test { name: "SettingsLibTests", - defaults: ["SettingsLibDefaults"], + defaults: [ + "SettingsLibDefaults", + "framework-wifi-test-defaults" + ], certificate: "platform", diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java index 61cbbd3eb0a4..03201ae6d5ba 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java @@ -83,7 +83,6 @@ import java.util.concurrent.CountDownLatch; @SmallTest @RunWith(AndroidJUnit4.class) public class AccessPointTest { - private static final String TEST_SSID = "\"test_ssid\""; private static final String ROAMING_SSID = "\"roaming_ssid\""; private static final String OSU_FRIENDLY_NAME = "osu_friendly_name"; @@ -98,6 +97,7 @@ public class AccessPointTest { 20 * DateUtils.MINUTE_IN_MILLIS; private Context mContext; + private int mMaxSignalLevel; private WifiInfo mWifiInfo; @Mock private Context mMockContext; @Mock private WifiManager mMockWifiManager; @@ -128,6 +128,7 @@ public class AccessPointTest { public void setUp() { MockitoAnnotations.initMocks(this); mContext = InstrumentationRegistry.getTargetContext(); + mMaxSignalLevel = mContext.getSystemService(WifiManager.class).getMaxSignalLevel(); mWifiInfo = new WifiInfo(); mWifiInfo.setSSID(WifiSsid.createFromAsciiEncoded(TEST_SSID)); mWifiInfo.setBSSID(TEST_BSSID); @@ -180,7 +181,7 @@ public class AccessPointTest { @Test public void testCompareTo_GivesHighLevelBeforeLowLevel() { - final int highLevel = AccessPoint.SIGNAL_LEVELS - 1; + final int highLevel = mMaxSignalLevel; final int lowLevel = 1; assertThat(highLevel).isGreaterThan(lowLevel); @@ -226,7 +227,7 @@ public class AccessPointTest { .setReachable(true).build(); AccessPoint saved = new TestAccessPointBuilder(mContext).setSaved(true).build(); AccessPoint highLevelAndReachable = new TestAccessPointBuilder(mContext) - .setLevel(AccessPoint.SIGNAL_LEVELS - 1).build(); + .setLevel(mMaxSignalLevel).build(); AccessPoint firstName = new TestAccessPointBuilder(mContext).setSsid("a").build(); AccessPoint lastname = new TestAccessPointBuilder(mContext).setSsid("z").build(); @@ -520,6 +521,8 @@ public class AccessPointTest { when(packageManager.getApplicationInfoAsUser(eq(appPackageName), anyInt(), anyInt())) .thenReturn(applicationInfo); when(applicationInfo.loadLabel(packageManager)).thenReturn(appLabel); + when(context.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager); + when(mMockWifiManager.calculateSignalLevel(rssi)).thenReturn(4); NetworkInfo networkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0 /* subtype */, "WIFI", ""); @@ -636,14 +639,14 @@ public class AccessPointTest { public void testBuilder_setLevel() { AccessPoint testAp; - for (int i = 0; i < AccessPoint.SIGNAL_LEVELS; i++) { + for (int i = 0; i <= mMaxSignalLevel; i++) { testAp = new TestAccessPointBuilder(mContext).setLevel(i).build(); assertThat(testAp.getLevel()).isEqualTo(i); } // numbers larger than the max level should be set to max - testAp = new TestAccessPointBuilder(mContext).setLevel(AccessPoint.SIGNAL_LEVELS).build(); - assertThat(testAp.getLevel()).isEqualTo(AccessPoint.SIGNAL_LEVELS - 1); + testAp = new TestAccessPointBuilder(mContext).setLevel(mMaxSignalLevel + 1).build(); + assertThat(testAp.getLevel()).isEqualTo(mMaxSignalLevel); // numbers less than 0 should give level 0 testAp = new TestAccessPointBuilder(mContext).setLevel(-100).build(); @@ -653,7 +656,7 @@ public class AccessPointTest { @Test public void testBuilder_settingReachableAfterLevelDoesNotAffectLevel() { int level = 1; - assertThat(level).isLessThan(AccessPoint.SIGNAL_LEVELS - 1); + assertThat(level).isLessThan(mMaxSignalLevel); AccessPoint testAp = new TestAccessPointBuilder(mContext).setLevel(level).setReachable(true).build(); diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/OWNER b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/OWNER new file mode 100644 index 000000000000..5c2a7b892f8f --- /dev/null +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/OWNER @@ -0,0 +1,4 @@ +# People who can approve changes for submission +arcwang@google.com +govenliu@google.com +qal@google.com diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java index 605c861fa07f..340a6c7f5d10 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java @@ -59,8 +59,9 @@ public class CategoryKeyTest { allKeys.add(CategoryKey.CATEGORY_SYSTEM_DEVELOPMENT); allKeys.add(CategoryKey.CATEGORY_GESTURES); allKeys.add(CategoryKey.CATEGORY_NIGHT_DISPLAY); + allKeys.add(CategoryKey.CATEGORY_SMART_BATTERY_SETTINGS); // DO NOT REMOVE ANYTHING ABOVE - assertThat(allKeys.size()).isEqualTo(18); + assertThat(allKeys.size()).isEqualTo(19); } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java index 894aa78a978e..c780a64c2fb4 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java @@ -127,7 +127,7 @@ public class LocalMediaManagerTest { mLocalMediaManager.registerCallback(mCallback); mLocalMediaManager.connectDevice(device); - verify(cachedDevice).connect(true); + verify(cachedDevice).connect(); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java index d98f50beadf5..b0a647e0dd2f 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java @@ -31,6 +31,7 @@ import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -71,12 +72,13 @@ public class DataUsageUtilsTest { when(mTelephonyManager.getSubscriberId(SUB_ID)).thenReturn(SUBSCRIBER_ID); when(mTelephonyManager.getSubscriberId(SUB_ID_2)).thenReturn(SUBSCRIBER_ID_2); when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager); - when(mSubscriptionManager.isActiveSubId(anyInt())).thenReturn(true); + when(mSubscriptionManager.isActiveSubscriptionId(anyInt())).thenReturn(true); } @Test + @Ignore public void getMobileTemplate_infoNull_returnMobileAll() { - when(mSubscriptionManager.isActiveSubId(SUB_ID)).thenReturn(false); + when(mSubscriptionManager.isActiveSubscriptionId(SUB_ID)).thenReturn(false); final NetworkTemplate networkTemplate = DataUsageUtils.getMobileTemplate(mContext, SUB_ID); assertThat(networkTemplate.matchesSubscriberId(SUBSCRIBER_ID)).isTrue(); @@ -84,6 +86,7 @@ public class DataUsageUtilsTest { } @Test + @Ignore public void getMobileTemplate_groupUuidNull_returnMobileAll() { when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(mInfo1); when(mInfo1.getGroupUuid()).thenReturn(null); @@ -96,6 +99,7 @@ public class DataUsageUtilsTest { } @Test + @Ignore public void getMobileTemplate_groupUuidExist_returnMobileMerged() { when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(mInfo1); when(mInfo1.getGroupUuid()).thenReturn(mParcelUuid); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java index 21aa526d7581..2bd20a933c58 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java @@ -72,7 +72,7 @@ public class AccessPointPreferenceTest { assertThat(AccessPointPreference.buildContentDescription( RuntimeEnvironment.application, pref, ap)) - .isEqualTo("ssid,connected,Wifi signal full.,Secure network"); + .isEqualTo("ssid,connected,Wifi disconnected.,Secure network"); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/OWNER b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/OWNER new file mode 100644 index 000000000000..5c2a7b892f8f --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/OWNER @@ -0,0 +1,4 @@ +# People who can approve changes for submission +arcwang@google.com +govenliu@google.com +qal@google.com diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java index 752a5498a077..0f1e0ff60e37 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java @@ -67,7 +67,7 @@ public class WifiEntryPreferenceTest { MockitoAnnotations.initMocks(this); when(mMockWifiEntry.getTitle()).thenReturn(MOCK_TITLE); - when(mMockWifiEntry.getSummary()).thenReturn(MOCK_SUMMARY); + when(mMockWifiEntry.getSummary(false /* concise */)).thenReturn(MOCK_SUMMARY); when(mMockIconInjector.getIcon(0)).thenReturn(mMockDrawable0); when(mMockIconInjector.getIcon(1)).thenReturn(mMockDrawable1); @@ -112,7 +112,7 @@ public class WifiEntryPreferenceTest { final WifiEntryPreference pref = new WifiEntryPreference(mContext, mMockWifiEntry, mMockIconInjector); final String updatedSummary = "updated summary"; - when(mMockWifiEntry.getSummary()).thenReturn(updatedSummary); + when(mMockWifiEntry.getSummary(false /* concise */)).thenReturn(updatedSummary); pref.refresh(); diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java index 72923a3554ab..dd94d2eb8fe0 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java @@ -141,9 +141,6 @@ public class GlobalSettingsValidators { VALIDATORS.put(Global.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.REQUIRE_PASSWORD_TO_DECRYPT, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.DEVICE_DEMO_MODE, BOOLEAN_VALIDATOR); - VALIDATORS.put(Global.WIFI_PNO_FREQUENCY_CULLING_ENABLED, BOOLEAN_VALIDATOR); - VALIDATORS.put(Global.WIFI_PNO_RECENCY_SORTING_ENABLED, BOOLEAN_VALIDATOR); - VALIDATORS.put(Global.WIFI_LINK_PROBING_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.AWARE_ALLOWED, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.POWER_BUTTON_LONG_PRESS, new InclusiveIntegerRangeValidator(0, 5)); VALIDATORS.put( diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java index 375a65044683..266bfe0a22b5 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java @@ -2554,7 +2554,7 @@ class DatabaseHelper extends SQLiteOpenHelper { for (int phoneId = 0; phoneId < phoneCount; phoneId++) { int mode = defaultNetworks.size() <= phoneId || defaultNetworks.get(phoneId) == null - ? RILConstants.PREFERRED_NETWORK_MODE : defaultNetworks.get(phoneId); + ? TelephonyManager.DEFAULT_PREFERRED_NETWORK_MODE : defaultNetworks.get(phoneId); if (phoneId > 0) val.append(','); val.append(mode); } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index aa36dca53bd9..449a135fa1c7 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -1070,9 +1070,6 @@ class SettingsProtoDumpUtil { Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE, GlobalSettingsProto.Network.RECOMMENDATIONS_PACKAGE); dumpSetting(s, p, - Settings.Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS, - GlobalSettingsProto.Network.RECOMMENDATION_REQUEST_TIMEOUT_MS); - dumpSetting(s, p, Settings.Global.NETWORK_WATCHLIST_ENABLED, GlobalSettingsProto.Network.WATCHLIST_ENABLED); dumpSetting(s, p, @@ -1587,9 +1584,6 @@ class SettingsProtoDumpUtil { Settings.Global.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED, GlobalSettingsProto.Wifi.WATCHDOG_POOR_NETWORK_TEST_ENABLED); dumpSetting(s, p, - Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED, - GlobalSettingsProto.Wifi.SUSPEND_OPTIMIZATIONS_ENABLED); - dumpSetting(s, p, Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED, GlobalSettingsProto.Wifi.VERBOSE_LOGGING_ENABLED); dumpSetting(s, p, diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 1e0c1d877f67..c913999ecb7c 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -20,6 +20,7 @@ import static android.os.Process.INVALID_UID; import static android.os.Process.ROOT_UID; import static android.os.Process.SHELL_UID; import static android.os.Process.SYSTEM_UID; +import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY; import android.Manifest; @@ -366,7 +367,9 @@ public class SettingsProvider extends ContentProvider { String value = getSettingValue(args); String tag = getSettingTag(args); final boolean makeDefault = getSettingMakeDefault(args); - insertGlobalSetting(name, value, tag, makeDefault, requestingUserId, false); + final boolean overrideableByRestore = getSettingOverrideableByRestore(args); + insertGlobalSetting(name, value, tag, makeDefault, requestingUserId, false, + overrideableByRestore); break; } @@ -374,13 +377,16 @@ public class SettingsProvider extends ContentProvider { String value = getSettingValue(args); String tag = getSettingTag(args); final boolean makeDefault = getSettingMakeDefault(args); - insertSecureSetting(name, value, tag, makeDefault, requestingUserId, false); + final boolean overrideableByRestore = getSettingOverrideableByRestore(args); + insertSecureSetting(name, value, tag, makeDefault, requestingUserId, false, + overrideableByRestore); break; } case Settings.CALL_METHOD_PUT_SYSTEM: { String value = getSettingValue(args); - insertSystemSetting(name, value, requestingUserId); + boolean overrideableByRestore = getSettingOverrideableByRestore(args); + insertSystemSetting(name, value, requestingUserId, overrideableByRestore); break; } @@ -575,20 +581,23 @@ public class SettingsProvider extends ContentProvider { switch (table) { case TABLE_GLOBAL: { if (insertGlobalSetting(name, value, null, false, - UserHandle.getCallingUserId(), false)) { + UserHandle.getCallingUserId(), false, + /* overrideableByRestore */ false)) { return Uri.withAppendedPath(Settings.Global.CONTENT_URI, name); } } break; case TABLE_SECURE: { if (insertSecureSetting(name, value, null, false, - UserHandle.getCallingUserId(), false)) { + UserHandle.getCallingUserId(), false, + /* overrideableByRestore */ false)) { return Uri.withAppendedPath(Settings.Secure.CONTENT_URI, name); } } break; case TABLE_SYSTEM: { - if (insertSystemSetting(name, value, UserHandle.getCallingUserId())) { + if (insertSystemSetting(name, value, UserHandle.getCallingUserId(), + /* overridableByRestore */ false)) { return Uri.withAppendedPath(Settings.System.CONTENT_URI, name); } } break; @@ -1074,7 +1083,8 @@ public class SettingsProvider extends ContentProvider { case MUTATION_OPERATION_INSERT: { return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM, name, value, null, makeDefault, true, - resolveCallingPackage(), false, null); + resolveCallingPackage(), false, null, + /* overrideableByRestore */ false); } case MUTATION_OPERATION_DELETE: { @@ -1178,14 +1188,15 @@ public class SettingsProvider extends ContentProvider { } private boolean insertGlobalSetting(String name, String value, String tag, - boolean makeDefault, int requestingUserId, boolean forceNotify) { + boolean makeDefault, int requestingUserId, boolean forceNotify, + boolean overrideableByRestore) { if (DEBUG) { Slog.v(LOG_TAG, "insertGlobalSetting(" + name + ", " + value + ", " + ", " + tag + ", " + makeDefault + ", " + requestingUserId + ", " + forceNotify + ")"); } return mutateGlobalSetting(name, value, tag, makeDefault, requestingUserId, - MUTATION_OPERATION_INSERT, forceNotify, 0); + MUTATION_OPERATION_INSERT, forceNotify, 0, overrideableByRestore); } private boolean deleteGlobalSetting(String name, int requestingUserId, boolean forceNotify) { @@ -1220,6 +1231,15 @@ public class SettingsProvider extends ContentProvider { private boolean mutateGlobalSetting(String name, String value, String tag, boolean makeDefault, int requestingUserId, int operation, boolean forceNotify, int mode) { + // overrideableByRestore = false as by default settings values shouldn't be overrideable by + // restore. + return mutateGlobalSetting(name, value, tag, makeDefault, requestingUserId, operation, + forceNotify, mode, /* overrideableByRestore */ false); + } + + private boolean mutateGlobalSetting(String name, String value, String tag, + boolean makeDefault, int requestingUserId, int operation, boolean forceNotify, + int mode, boolean overrideableByRestore) { // Make sure the caller can change the settings - treated as secure. enforceWritePermission(Manifest.permission.WRITE_SECURE_SETTINGS); @@ -1238,7 +1258,8 @@ public class SettingsProvider extends ContentProvider { case MUTATION_OPERATION_INSERT: { return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM, name, value, tag, makeDefault, - getCallingPackage(), forceNotify, CRITICAL_GLOBAL_SETTINGS); + getCallingPackage(), forceNotify, + CRITICAL_GLOBAL_SETTINGS, overrideableByRestore); } case MUTATION_OPERATION_DELETE: { @@ -1474,7 +1495,7 @@ public class SettingsProvider extends ContentProvider { ) { @Override public boolean update(String value, boolean setDefault, String packageName, - String tag, boolean forceNonSystemPackage) { + String tag, boolean forceNonSystemPackage, boolean overrideableByRestore) { Slog.wtf(LOG_TAG, "update shouldn't be called on this instance."); return false; } @@ -1483,14 +1504,15 @@ public class SettingsProvider extends ContentProvider { } private boolean insertSecureSetting(String name, String value, String tag, - boolean makeDefault, int requestingUserId, boolean forceNotify) { + boolean makeDefault, int requestingUserId, boolean forceNotify, + boolean overrideableByRestore) { if (DEBUG) { Slog.v(LOG_TAG, "insertSecureSetting(" + name + ", " + value + ", " + ", " + tag + ", " + makeDefault + ", " + requestingUserId + ", " + forceNotify + ")"); } return mutateSecureSetting(name, value, tag, makeDefault, requestingUserId, - MUTATION_OPERATION_INSERT, forceNotify, 0); + MUTATION_OPERATION_INSERT, forceNotify, 0, overrideableByRestore); } private boolean deleteSecureSetting(String name, int requestingUserId, boolean forceNotify) { @@ -1528,6 +1550,15 @@ public class SettingsProvider extends ContentProvider { private boolean mutateSecureSetting(String name, String value, String tag, boolean makeDefault, int requestingUserId, int operation, boolean forceNotify, int mode) { + // overrideableByRestore = false as by default settings values shouldn't be overrideable by + // restore. + return mutateSecureSetting(name, value, tag, makeDefault, requestingUserId, operation, + forceNotify, mode, /* overrideableByRestore */ false); + } + + private boolean mutateSecureSetting(String name, String value, String tag, + boolean makeDefault, int requestingUserId, int operation, boolean forceNotify, + int mode, boolean overrideableByRestore) { // Make sure the caller can change the settings. enforceWritePermission(Manifest.permission.WRITE_SECURE_SETTINGS); @@ -1560,7 +1591,8 @@ public class SettingsProvider extends ContentProvider { case MUTATION_OPERATION_INSERT: { return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SECURE, owningUserId, name, value, tag, makeDefault, - getCallingPackage(), forceNotify, CRITICAL_SECURE_SETTINGS); + getCallingPackage(), forceNotify, CRITICAL_SECURE_SETTINGS, + overrideableByRestore); } case MUTATION_OPERATION_DELETE: { @@ -1636,13 +1668,15 @@ public class SettingsProvider extends ContentProvider { } } - private boolean insertSystemSetting(String name, String value, int requestingUserId) { + private boolean insertSystemSetting(String name, String value, int requestingUserId, + boolean overrideableByRestore) { if (DEBUG) { Slog.v(LOG_TAG, "insertSystemSetting(" + name + ", " + value + ", " + requestingUserId + ")"); } - return mutateSystemSetting(name, value, requestingUserId, MUTATION_OPERATION_INSERT); + return mutateSystemSetting(name, value, requestingUserId, MUTATION_OPERATION_INSERT, + overrideableByRestore); } private boolean deleteSystemSetting(String name, int requestingUserId) { @@ -1662,8 +1696,15 @@ public class SettingsProvider extends ContentProvider { return mutateSystemSetting(name, value, requestingUserId, MUTATION_OPERATION_UPDATE); } - private boolean mutateSystemSetting(String name, String value, int runAsUserId, - int operation) { + private boolean mutateSystemSetting(String name, String value, int runAsUserId, int operation) { + // overrideableByRestore = false as by default settings values shouldn't be overrideable by + // restore. + return mutateSystemSetting(name, value, runAsUserId, operation, + /* overrideableByRestore */ false); + } + + private boolean mutateSystemSetting(String name, String value, int runAsUserId, int operation, + boolean overrideableByRestore) { if (!hasWriteSecureSettingsPermission()) { // If the caller doesn't hold WRITE_SECURE_SETTINGS, we verify whether this // operation is allowed for the calling package through appops. @@ -1713,7 +1754,7 @@ public class SettingsProvider extends ContentProvider { validateSystemSettingValue(name, value); return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SYSTEM, owningUserId, name, value, null, false, getCallingPackage(), - false, null); + false, null, overrideableByRestore); } case MUTATION_OPERATION_DELETE: { @@ -2050,7 +2091,8 @@ public class SettingsProvider extends ContentProvider { } return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SECURE, owningUserId, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, newProviders, tag, - makeDefault, getCallingPackage(), forceNotify, CRITICAL_SECURE_SETTINGS); + makeDefault, getCallingPackage(), forceNotify, CRITICAL_SECURE_SETTINGS, + /* overrideableByRestore */ false); } private static void warnOrThrowForUndesiredSecureSettingsMutationForTargetSdk( @@ -2144,6 +2186,10 @@ public class SettingsProvider extends ContentProvider { return (args != null) && args.getBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY); } + private static boolean getSettingOverrideableByRestore(Bundle args) { + return (args != null) && args.getBoolean(Settings.CALL_METHOD_OVERRIDEABLE_BY_RESTORE_KEY); + } + private static int getResetModeEnforcingPermission(Bundle args) { final int mode = (args != null) ? args.getInt(Settings.CALL_METHOD_RESET_MODE_KEY) : 0; switch (mode) { @@ -2641,21 +2687,21 @@ public class SettingsProvider extends ContentProvider { public boolean insertSettingLocked(int type, int userId, String name, String value, String tag, boolean makeDefault, String packageName, boolean forceNotify, - Set<String> criticalSettings) { + Set<String> criticalSettings, boolean overrideableByRestore) { return insertSettingLocked(type, userId, name, value, tag, makeDefault, false, - packageName, forceNotify, criticalSettings); + packageName, forceNotify, criticalSettings, overrideableByRestore); } public boolean insertSettingLocked(int type, int userId, String name, String value, String tag, boolean makeDefault, boolean forceNonSystemPackage, String packageName, - boolean forceNotify, Set<String> criticalSettings) { + boolean forceNotify, Set<String> criticalSettings, boolean overrideableByRestore) { final int key = makeKey(type, userId); boolean success = false; SettingsState settingsState = peekSettingsStateLocked(key); if (settingsState != null) { success = settingsState.insertSettingLocked(name, value, - tag, makeDefault, forceNonSystemPackage, packageName); + tag, makeDefault, forceNonSystemPackage, packageName, overrideableByRestore); } if (success && criticalSettings != null && criticalSettings.contains(name)) { @@ -3304,7 +3350,7 @@ public class SettingsProvider extends ContentProvider { } private final class UpgradeController { - private static final int SETTINGS_VERSION = 185; + private static final int SETTINGS_VERSION = 186; private final int mUserId; @@ -3390,6 +3436,10 @@ public class SettingsProvider extends ContentProvider { * for this user from the old to the new version. When you add a new * upgrade step you *must* update SETTINGS_VERSION. * + * All settings modifications should be made through + * {@link SettingsState#insertSettingOverrideableByRestoreLocked(String, String, String, + * boolean, String)} so that restore can override those values if needed. + * * This is an example of moving a setting from secure to global. * * // v119: Example settings changes. @@ -3435,7 +3485,8 @@ public class SettingsProvider extends ContentProvider { // v120: Add double tap to wake setting. if (currentVersion == 119) { SettingsState secureSettings = getSecureSettingsLocked(userId); - secureSettings.insertSettingLocked(Settings.Secure.DOUBLE_TAP_TO_WAKE, + secureSettings.insertSettingOverrideableByRestoreLocked( + Settings.Secure.DOUBLE_TAP_TO_WAKE, getContext().getResources().getBoolean( R.bool.def_double_tap_to_wake) ? "1" : "0", null, true, SettingsState.SYSTEM_PACKAGE_NAME); @@ -3460,7 +3511,7 @@ public class SettingsProvider extends ContentProvider { Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT); if (defaultComponent != null && !defaultComponent.isEmpty() && currentSetting.isNull()) { - secureSettings.insertSettingLocked( + secureSettings.insertSettingOverrideableByRestoreLocked( Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT, defaultComponent, null, true, SettingsState.SYSTEM_PACKAGE_NAME); } @@ -3475,7 +3526,7 @@ public class SettingsProvider extends ContentProvider { Setting currentSetting = globalSettings.getSettingLocked( Settings.Global.ADD_USERS_WHEN_LOCKED); if (currentSetting.isNull()) { - globalSettings.insertSettingLocked( + globalSettings.insertSettingOverrideableByRestoreLocked( Settings.Global.ADD_USERS_WHEN_LOCKED, getContext().getResources().getBoolean( R.bool.def_add_users_from_lockscreen) ? "1" : "0", @@ -3489,8 +3540,9 @@ public class SettingsProvider extends ContentProvider { final SettingsState globalSettings = getGlobalSettingsLocked(); String defaultDisabledProfiles = (getContext().getResources().getString( R.string.def_bluetooth_disabled_profiles)); - globalSettings.insertSettingLocked(Settings.Global.BLUETOOTH_DISABLED_PROFILES, - defaultDisabledProfiles, null, true, SettingsState.SYSTEM_PACKAGE_NAME); + globalSettings.insertSettingOverrideableByRestoreLocked( + Settings.Global.BLUETOOTH_DISABLED_PROFILES, defaultDisabledProfiles, + null, true, SettingsState.SYSTEM_PACKAGE_NAME); currentVersion = 124; } @@ -3501,7 +3553,7 @@ public class SettingsProvider extends ContentProvider { Setting currentSetting = secureSettings.getSettingLocked( Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD); if (currentSetting.isNull()) { - secureSettings.insertSettingLocked( + secureSettings.insertSettingOverrideableByRestoreLocked( Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, getContext().getResources().getBoolean( R.bool.def_show_ime_with_hard_keyboard) ? "1" : "0", @@ -3530,7 +3582,7 @@ public class SettingsProvider extends ContentProvider { b.append(c.flattenToString()); start = false; } - secureSettings.insertSettingLocked( + secureSettings.insertSettingOverrideableByRestoreLocked( Settings.Secure.ENABLED_VR_LISTENERS, b.toString(), null, true, SettingsState.SYSTEM_PACKAGE_NAME); } @@ -3550,7 +3602,7 @@ public class SettingsProvider extends ContentProvider { Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS); if (!showNotifications.isNull()) { final SettingsState secureSettings = getSecureSettingsLocked(userId); - secureSettings.insertSettingLocked( + secureSettings.insertSettingOverrideableByRestoreLocked( Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, showNotifications.getValue(), null, true, SettingsState.SYSTEM_PACKAGE_NAME); @@ -3560,7 +3612,7 @@ public class SettingsProvider extends ContentProvider { Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); if (!allowPrivate.isNull()) { final SettingsState secureSettings = getSecureSettingsLocked(userId); - secureSettings.insertSettingLocked( + secureSettings.insertSettingOverrideableByRestoreLocked( Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, allowPrivate.getValue(), null, true, SettingsState.SYSTEM_PACKAGE_NAME); @@ -3587,7 +3639,7 @@ public class SettingsProvider extends ContentProvider { final String oldValue = systemSecureSettings.getSettingLocked( Settings.Secure.LONG_PRESS_TIMEOUT).getValue(); if (TextUtils.equals("500", oldValue)) { - systemSecureSettings.insertSettingLocked( + systemSecureSettings.insertSettingOverrideableByRestoreLocked( Settings.Secure.LONG_PRESS_TIMEOUT, String.valueOf(getContext().getResources().getInteger( R.integer.def_long_press_timeout_millis)), @@ -3603,10 +3655,12 @@ public class SettingsProvider extends ContentProvider { getSettingLocked(Settings.Secure.DOZE_ENABLED).getValue()); if (dozeExplicitlyDisabled) { - secureSettings.insertSettingLocked(Settings.Secure.DOZE_PICK_UP_GESTURE, - "0", null, true, SettingsState.SYSTEM_PACKAGE_NAME); - secureSettings.insertSettingLocked(Settings.Secure.DOZE_DOUBLE_TAP_GESTURE, - "0", null, true, SettingsState.SYSTEM_PACKAGE_NAME); + secureSettings.insertSettingOverrideableByRestoreLocked( + Settings.Secure.DOZE_PICK_UP_GESTURE, "0", null, true, + SettingsState.SYSTEM_PACKAGE_NAME); + secureSettings.insertSettingOverrideableByRestoreLocked( + Settings.Secure.DOZE_DOUBLE_TAP_GESTURE, "0", null, true, + SettingsState.SYSTEM_PACKAGE_NAME); } currentVersion = 131; } @@ -3617,7 +3671,7 @@ public class SettingsProvider extends ContentProvider { final String oldValue = systemSecureSettings.getSettingLocked( Settings.Secure.MULTI_PRESS_TIMEOUT).getValue(); if (TextUtils.equals(null, oldValue)) { - systemSecureSettings.insertSettingLocked( + systemSecureSettings.insertSettingOverrideableByRestoreLocked( Settings.Secure.MULTI_PRESS_TIMEOUT, String.valueOf(getContext().getResources().getInteger( R.integer.def_multi_press_timeout_millis)), @@ -3632,7 +3686,7 @@ public class SettingsProvider extends ContentProvider { final SettingsState systemSecureSettings = getSecureSettingsLocked(userId); String defaultSyncParentSounds = (getContext().getResources() .getBoolean(R.bool.def_sync_parent_sounds) ? "1" : "0"); - systemSecureSettings.insertSettingLocked( + systemSecureSettings.insertSettingOverrideableByRestoreLocked( Settings.Secure.SYNC_PARENT_SOUNDS, defaultSyncParentSounds, null, true, SettingsState.SYSTEM_PACKAGE_NAME); currentVersion = 133; @@ -3645,9 +3699,9 @@ public class SettingsProvider extends ContentProvider { .isNull()) { String defaultEndButtonBehavior = Integer.toString(getContext() .getResources().getInteger(R.integer.def_end_button_behavior)); - systemSettings.insertSettingLocked(Settings.System.END_BUTTON_BEHAVIOR, - defaultEndButtonBehavior, null, true, - SettingsState.SYSTEM_PACKAGE_NAME); + systemSettings.insertSettingOverrideableByRestoreLocked( + Settings.System.END_BUTTON_BEHAVIOR, defaultEndButtonBehavior, null, + true, SettingsState.SYSTEM_PACKAGE_NAME); } currentVersion = 134; } @@ -3705,8 +3759,8 @@ public class SettingsProvider extends ContentProvider { if (ssaid.isNull() || ssaid.getValue() == null) { // Android Id doesn't exist for this package so create it. - ssaidSettings.insertSettingLocked(uid, legacySsaid, null, true, - info.packageName); + ssaidSettings.insertSettingOverrideableByRestoreLocked(uid, + legacySsaid, null, true, info.packageName); if (DEBUG) { Slog.d(LOG_TAG, "Keep the legacy ssaid for uid=" + uid); } @@ -3726,13 +3780,14 @@ public class SettingsProvider extends ContentProvider { && secureSetting.getSettingLocked( Settings.Secure.INSTALL_NON_MARKET_APPS).getValue().equals("0")) { - secureSetting.insertSettingLocked(Settings.Secure.INSTALL_NON_MARKET_APPS, - "1", null, true, SettingsState.SYSTEM_PACKAGE_NAME); + secureSetting.insertSettingOverrideableByRestoreLocked( + Settings.Secure.INSTALL_NON_MARKET_APPS, "1", null, true, + SettingsState.SYSTEM_PACKAGE_NAME); // For managed profiles with profile owners, DevicePolicyManagerService // may want to set the user restriction in this case - secureSetting.insertSettingLocked( - Settings.Secure.UNKNOWN_SOURCES_DEFAULT_REVERSED, "1", null, true, - SettingsState.SYSTEM_PACKAGE_NAME); + secureSetting.insertSettingOverrideableByRestoreLocked( + Settings.Secure.UNKNOWN_SOURCES_DEFAULT_REVERSED, "1", null, + true, SettingsState.SYSTEM_PACKAGE_NAME); } currentVersion = 138; } @@ -3773,7 +3828,7 @@ public class SettingsProvider extends ContentProvider { Setting currentSetting = globalSettings.getSettingLocked( Settings.Global.WIFI_WAKEUP_ENABLED); if (currentSetting.isNull()) { - globalSettings.insertSettingLocked( + globalSettings.insertSettingOverrideableByRestoreLocked( Settings.Global.WIFI_WAKEUP_ENABLED, getContext().getResources().getBoolean( R.bool.def_wifi_wakeup_enabled) ? "1" : "0", @@ -3795,8 +3850,9 @@ public class SettingsProvider extends ContentProvider { if (defaultValue != null) { Slog.d(LOG_TAG, "Setting [" + defaultValue + "] as Autofill Service " + "for user " + userId); - secureSettings.insertSettingLocked(Settings.Secure.AUTOFILL_SERVICE, - defaultValue, null, true, SettingsState.SYSTEM_PACKAGE_NAME); + secureSettings.insertSettingOverrideableByRestoreLocked( + Settings.Secure.AUTOFILL_SERVICE, defaultValue, null, true, + SettingsState.SYSTEM_PACKAGE_NAME); } } @@ -3847,7 +3903,7 @@ public class SettingsProvider extends ContentProvider { final Setting currentSetting = globalSettings.getSettingLocked( Global.DEFAULT_RESTRICT_BACKGROUND_DATA); if (currentSetting.isNull()) { - globalSettings.insertSettingLocked( + globalSettings.insertSettingOverrideableByRestoreLocked( Global.DEFAULT_RESTRICT_BACKGROUND_DATA, getContext().getResources().getBoolean( R.bool.def_restrict_background_data) ? "1" : "0", @@ -3866,7 +3922,7 @@ public class SettingsProvider extends ContentProvider { final String defaultValue = getContext().getResources().getString( R.string.def_backup_manager_constants); if (!TextUtils.isEmpty(defaultValue)) { - systemSecureSettings.insertSettingLocked( + systemSecureSettings.insertSettingOverrideableByRestoreLocked( Settings.Secure.BACKUP_MANAGER_CONSTANTS, defaultValue, null, true, SettingsState.SYSTEM_PACKAGE_NAME); } @@ -3880,7 +3936,7 @@ public class SettingsProvider extends ContentProvider { final Setting currentSetting = globalSettings.getSettingLocked( Settings.Global.MOBILE_DATA_ALWAYS_ON); if (currentSetting.isNull()) { - globalSettings.insertSettingLocked( + globalSettings.insertSettingOverrideableByRestoreLocked( Settings.Global.MOBILE_DATA_ALWAYS_ON, getContext().getResources().getBoolean( R.bool.def_mobile_data_always_on) ? "1" : "0", @@ -3916,7 +3972,7 @@ public class SettingsProvider extends ContentProvider { if (showNotificationBadges.isNull()) { final boolean defaultValue = getContext().getResources().getBoolean( com.android.internal.R.bool.config_notificationBadging); - systemSecureSettings.insertSettingLocked( + systemSecureSettings.insertSettingOverrideableByRestoreLocked( Secure.NOTIFICATION_BADGING, defaultValue ? "1" : "0", null, true, SettingsState.SYSTEM_PACKAGE_NAME); @@ -3933,7 +3989,7 @@ public class SettingsProvider extends ContentProvider { final String defaultValue = getContext().getResources().getString( R.string.def_backup_local_transport_parameters); if (!TextUtils.isEmpty(defaultValue)) { - systemSecureSettings.insertSettingLocked( + systemSecureSettings.insertSettingOverrideableByRestoreLocked( Settings.Secure.BACKUP_LOCAL_TRANSPORT_PARAMETERS, defaultValue, null, true, SettingsState.SYSTEM_PACKAGE_NAME); } @@ -3956,7 +4012,7 @@ public class SettingsProvider extends ContentProvider { if (currentSetting.isNull()) { String defaultZenDuration = Integer.toString(getContext() .getResources().getInteger(R.integer.def_zen_duration)); - globalSettings.insertSettingLocked( + globalSettings.insertSettingOverrideableByRestoreLocked( Global.ZEN_DURATION, defaultZenDuration, null, true, SettingsState.SYSTEM_PACKAGE_NAME); } @@ -3972,7 +4028,7 @@ public class SettingsProvider extends ContentProvider { final String defaultValue = getContext().getResources().getString( R.string.def_backup_agent_timeout_parameters); if (!TextUtils.isEmpty(defaultValue)) { - globalSettings.insertSettingLocked( + globalSettings.insertSettingOverrideableByRestoreLocked( Settings.Global.BACKUP_AGENT_TIMEOUT_PARAMETERS, defaultValue, null, true, SettingsState.SYSTEM_PACKAGE_NAME); @@ -4001,7 +4057,7 @@ public class SettingsProvider extends ContentProvider { Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS); // The default value is "1", check if user has turned it off. if ("0".equals(showNotifications.getValue())) { - secureSettings.insertSettingLocked( + secureSettings.insertSettingOverrideableByRestoreLocked( Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, "0", null /* tag */, false /* makeDefault */, SettingsState.SYSTEM_PACKAGE_NAME); @@ -4022,7 +4078,7 @@ public class SettingsProvider extends ContentProvider { String oldValue = globalSettings.getSettingLocked( Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY).getValue(); if (TextUtils.equals(null, oldValue)) { - globalSettings.insertSettingLocked( + globalSettings.insertSettingOverrideableByRestoreLocked( Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY, Integer.toString(getContext().getResources().getInteger( R.integer.def_max_sound_trigger_detection_service_ops_per_day)), @@ -4032,7 +4088,7 @@ public class SettingsProvider extends ContentProvider { oldValue = globalSettings.getSettingLocked( Global.SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT).getValue(); if (TextUtils.equals(null, oldValue)) { - globalSettings.insertSettingLocked( + globalSettings.insertSettingOverrideableByRestoreLocked( Settings.Global.SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT, Integer.toString(getContext().getResources().getInteger( R.integer.def_sound_trigger_detection_service_op_timeout)), @@ -4047,7 +4103,7 @@ public class SettingsProvider extends ContentProvider { final Setting currentSetting = secureSettings.getSettingLocked( Secure.VOLUME_HUSH_GESTURE); if (currentSetting.isNull()) { - secureSettings.insertSettingLocked( + secureSettings.insertSettingOverrideableByRestoreLocked( Secure.VOLUME_HUSH_GESTURE, Integer.toString(Secure.VOLUME_HUSH_VIBRATE), null, true, SettingsState.SYSTEM_PACKAGE_NAME); @@ -4068,7 +4124,7 @@ public class SettingsProvider extends ContentProvider { final Setting currentSetting = settings.getSettingLocked( Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY); if (currentSetting.isDefaultFromSystem()) { - settings.insertSettingLocked( + settings.insertSettingOverrideableByRestoreLocked( Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY, Integer.toString(getContext().getResources().getInteger( R.integer @@ -4097,7 +4153,7 @@ public class SettingsProvider extends ContentProvider { Setting currentHushUsedSetting = secureSettings.getSettingLocked( Secure.HUSH_GESTURE_USED); if (currentHushUsedSetting.isNull()) { - secureSettings.insertSettingLocked( + secureSettings.insertSettingOverrideableByRestoreLocked( Settings.Secure.HUSH_GESTURE_USED, "0", null, true, SettingsState.SYSTEM_PACKAGE_NAME); } @@ -4105,7 +4161,7 @@ public class SettingsProvider extends ContentProvider { Setting currentRingerToggleCountSetting = secureSettings.getSettingLocked( Secure.MANUAL_RINGER_TOGGLE_COUNT); if (currentRingerToggleCountSetting.isNull()) { - secureSettings.insertSettingLocked( + secureSettings.insertSettingOverrideableByRestoreLocked( Settings.Secure.MANUAL_RINGER_TOGGLE_COUNT, "0", null, true, SettingsState.SYSTEM_PACKAGE_NAME); } @@ -4124,7 +4180,7 @@ public class SettingsProvider extends ContentProvider { final Setting currentSetting = systemSettings.getSettingLocked( Settings.System.VIBRATE_WHEN_RINGING); if (currentSetting.isNull()) { - systemSettings.insertSettingLocked( + systemSettings.insertSettingOverrideableByRestoreLocked( Settings.System.VIBRATE_WHEN_RINGING, getContext().getResources().getBoolean( R.bool.def_vibrate_when_ringing) ? "1" : "0", @@ -4148,18 +4204,18 @@ public class SettingsProvider extends ContentProvider { // ZEN_DURATION if (!globalZenDuration.isNull()) { - secureSettings.insertSettingLocked( + secureSettings.insertSettingOverrideableByRestoreLocked( Secure.ZEN_DURATION, globalZenDuration.getValue(), null, false, SettingsState.SYSTEM_PACKAGE_NAME); // set global zen duration setting to null since it's deprecated - globalSettings.insertSettingLocked( + globalSettings.insertSettingOverrideableByRestoreLocked( Global.ZEN_DURATION, null, null, true, SettingsState.SYSTEM_PACKAGE_NAME); } else if (secureZenDuration.isNull()) { String defaultZenDuration = Integer.toString(getContext() .getResources().getInteger(R.integer.def_zen_duration)); - secureSettings.insertSettingLocked( + secureSettings.insertSettingOverrideableByRestoreLocked( Secure.ZEN_DURATION, defaultZenDuration, null, true, SettingsState.SYSTEM_PACKAGE_NAME); } @@ -4168,7 +4224,7 @@ public class SettingsProvider extends ContentProvider { final Setting currentShowZenSettingSuggestion = secureSettings.getSettingLocked( Secure.SHOW_ZEN_SETTINGS_SUGGESTION); if (currentShowZenSettingSuggestion.isNull()) { - secureSettings.insertSettingLocked( + secureSettings.insertSettingOverrideableByRestoreLocked( Secure.SHOW_ZEN_SETTINGS_SUGGESTION, "1", null, true, SettingsState.SYSTEM_PACKAGE_NAME); } @@ -4177,7 +4233,7 @@ public class SettingsProvider extends ContentProvider { final Setting currentUpdatedSetting = secureSettings.getSettingLocked( Secure.ZEN_SETTINGS_UPDATED); if (currentUpdatedSetting.isNull()) { - secureSettings.insertSettingLocked( + secureSettings.insertSettingOverrideableByRestoreLocked( Secure.ZEN_SETTINGS_UPDATED, "0", null, true, SettingsState.SYSTEM_PACKAGE_NAME); } @@ -4186,7 +4242,7 @@ public class SettingsProvider extends ContentProvider { final Setting currentSettingSuggestionViewed = secureSettings.getSettingLocked( Secure.ZEN_SETTINGS_SUGGESTION_VIEWED); if (currentSettingSuggestionViewed.isNull()) { - secureSettings.insertSettingLocked( + secureSettings.insertSettingOverrideableByRestoreLocked( Secure.ZEN_SETTINGS_SUGGESTION_VIEWED, "0", null, true, SettingsState.SYSTEM_PACKAGE_NAME); } @@ -4209,20 +4265,20 @@ public class SettingsProvider extends ContentProvider { if (!globalChargingSoundEnabled.isNull()) { if (secureChargingSoundsEnabled.isNull()) { - secureSettings.insertSettingLocked( + secureSettings.insertSettingOverrideableByRestoreLocked( Secure.CHARGING_SOUNDS_ENABLED, globalChargingSoundEnabled.getValue(), null, false, SettingsState.SYSTEM_PACKAGE_NAME); } // set global charging_sounds_enabled setting to null since it's deprecated - globalSettings.insertSettingLocked( + globalSettings.insertSettingOverrideableByRestoreLocked( Global.CHARGING_SOUNDS_ENABLED, null, null, true, SettingsState.SYSTEM_PACKAGE_NAME); } else if (secureChargingSoundsEnabled.isNull()) { String defChargingSoundsEnabled = getContext().getResources() .getBoolean(R.bool.def_charging_sounds_enabled) ? "1" : "0"; - secureSettings.insertSettingLocked( + secureSettings.insertSettingOverrideableByRestoreLocked( Secure.CHARGING_SOUNDS_ENABLED, defChargingSoundsEnabled, null, true, SettingsState.SYSTEM_PACKAGE_NAME); } @@ -4234,7 +4290,7 @@ public class SettingsProvider extends ContentProvider { if (secureChargingVibrationEnabled.isNull()) { String defChargingVibrationEnabled = getContext().getResources() .getBoolean(R.bool.def_charging_vibration_enabled) ? "1" : "0"; - secureSettings.insertSettingLocked( + secureSettings.insertSettingOverrideableByRestoreLocked( Secure.CHARGING_VIBRATION_ENABLED, defChargingVibrationEnabled, null, true, SettingsState.SYSTEM_PACKAGE_NAME); } @@ -4254,7 +4310,7 @@ public class SettingsProvider extends ContentProvider { currentSetting.getValue()); if ((currentSettingIntegerValue & (1 << AudioManager.STREAM_VOICE_CALL)) == 0) { - systemSettings.insertSettingLocked( + systemSettings.insertSettingOverrideableByRestoreLocked( Settings.System.MUTE_STREAMS_AFFECTED, Integer.toString( currentSettingIntegerValue @@ -4295,7 +4351,7 @@ public class SettingsProvider extends ContentProvider { ? Secure.LOCATION_MODE_ON : Secure.LOCATION_MODE_OFF; } - secureSettings.insertSettingLocked( + secureSettings.insertSettingOverrideableByRestoreLocked( Secure.LOCATION_MODE, Integer.toString(defLocationMode), null, true, SettingsState.SYSTEM_PACKAGE_NAME); } @@ -4317,7 +4373,7 @@ public class SettingsProvider extends ContentProvider { Setting currentRampingRingerSetting = globalSettings.getSettingLocked( Settings.Global.APPLY_RAMPING_RINGER); if (currentRampingRingerSetting.isNull()) { - globalSettings.insertSettingLocked( + globalSettings.insertSettingOverrideableByRestoreLocked( Settings.Global.APPLY_RAMPING_RINGER, getContext().getResources().getBoolean( R.bool.def_apply_ramping_ringer) ? "1" : "0", null, @@ -4343,7 +4399,7 @@ public class SettingsProvider extends ContentProvider { if (!notificationVibrationIntensity.isNull() && ringVibrationIntensity.isNull()) { - systemSettings.insertSettingLocked( + systemSettings.insertSettingOverrideableByRestoreLocked( Settings.System.RING_VIBRATION_INTENSITY, notificationVibrationIntensity.getValue(), null , true, SettingsState.SYSTEM_PACKAGE_NAME); @@ -4387,7 +4443,7 @@ public class SettingsProvider extends ContentProvider { if (awareEnabled.isNull()) { final boolean defAwareEnabled = getContext().getResources().getBoolean( R.bool.def_aware_enabled); - secureSettings.insertSettingLocked( + secureSettings.insertSettingOverrideableByRestoreLocked( Secure.AWARE_ENABLED, defAwareEnabled ? "1" : "0", null, true, SettingsState.SYSTEM_PACKAGE_NAME); } @@ -4407,7 +4463,7 @@ public class SettingsProvider extends ContentProvider { if (skipGesture.isNull()) { final boolean defSkipGesture = getContext().getResources().getBoolean( R.bool.def_skip_gesture); - secureSettings.insertSettingLocked( + secureSettings.insertSettingOverrideableByRestoreLocked( Secure.SKIP_GESTURE, defSkipGesture ? "1" : "0", null, true, SettingsState.SYSTEM_PACKAGE_NAME); } @@ -4418,7 +4474,7 @@ public class SettingsProvider extends ContentProvider { if (silenceGesture.isNull()) { final boolean defSilenceGesture = getContext().getResources().getBoolean( R.bool.def_silence_gesture); - secureSettings.insertSettingLocked( + secureSettings.insertSettingOverrideableByRestoreLocked( Secure.SILENCE_GESTURE, defSilenceGesture ? "1" : "0", null, true, SettingsState.SYSTEM_PACKAGE_NAME); } @@ -4446,7 +4502,7 @@ public class SettingsProvider extends ContentProvider { if (awareLockEnabled.isNull()) { final boolean defAwareLockEnabled = getContext().getResources().getBoolean( R.bool.def_aware_lock_enabled); - secureSettings.insertSettingLocked( + secureSettings.insertSettingOverrideableByRestoreLocked( Secure.AWARE_LOCK_ENABLED, defAwareLockEnabled ? "1" : "0", null, true, SettingsState.SYSTEM_PACKAGE_NAME); } @@ -4466,7 +4522,7 @@ public class SettingsProvider extends ContentProvider { currentSetting.getValue()); if ((currentSettingIntegerValue & (1 << AudioManager.STREAM_BLUETOOTH_SCO)) == 0) { - systemSettings.insertSettingLocked( + systemSettings.insertSettingOverrideableByRestoreLocked( Settings.System.MUTE_STREAMS_AFFECTED, Integer.toString( currentSettingIntegerValue @@ -4512,13 +4568,13 @@ public class SettingsProvider extends ContentProvider { if (oldValueWireless == null || TextUtils.equals(oldValueWireless, defaultValueWired)) { if (!TextUtils.isEmpty(defaultValueWireless)) { - globalSettings.insertSettingLocked( + globalSettings.insertSettingOverrideableByRestoreLocked( Global.WIRELESS_CHARGING_STARTED_SOUND, defaultValueWireless, null /* tag */, true /* makeDefault */, SettingsState.SYSTEM_PACKAGE_NAME); } else if (!TextUtils.isEmpty(defaultValueWired)) { // if the wireless sound is empty, use the wired charging sound - globalSettings.insertSettingLocked( + globalSettings.insertSettingOverrideableByRestoreLocked( Global.WIRELESS_CHARGING_STARTED_SOUND, defaultValueWired, null /* tag */, true /* makeDefault */, SettingsState.SYSTEM_PACKAGE_NAME); @@ -4527,7 +4583,7 @@ public class SettingsProvider extends ContentProvider { // wired charging sound if (oldValueWired == null && !TextUtils.isEmpty(defaultValueWired)) { - globalSettings.insertSettingLocked( + globalSettings.insertSettingOverrideableByRestoreLocked( Global.CHARGING_STARTED_SOUND, defaultValueWired, null /* tag */, true /* makeDefault */, SettingsState.SYSTEM_PACKAGE_NAME); @@ -4539,14 +4595,40 @@ public class SettingsProvider extends ContentProvider { // Version 184: Reset the default for Global Settings: NOTIFICATION_BUBBLES // This is originally set in version 182, however, the default value changed // so this step is to ensure the value is updated to the correct default. - getGlobalSettingsLocked().insertSettingLocked(Global.NOTIFICATION_BUBBLES, - getContext().getResources().getBoolean( + getGlobalSettingsLocked().insertSettingOverrideableByRestoreLocked( + Global.NOTIFICATION_BUBBLES, getContext().getResources().getBoolean( R.bool.def_notification_bubbles) ? "1" : "0", null /* tag */, true /* makeDefault */, SettingsState.SYSTEM_PACKAGE_NAME); currentVersion = 185; } + if (currentVersion == 185) { + // Deprecate ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, and migrate it + // to ACCESSIBILITY_BUTTON_TARGET_COMPONENT. + final SettingsState secureSettings = getSecureSettingsLocked(userId); + final Setting magnifyNavbarEnabled = secureSettings.getSettingLocked( + Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED); + if ("1".equals(magnifyNavbarEnabled.getValue())) { + secureSettings.insertSettingLocked( + Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT, + ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER, + null /* tag */, false /* makeDefault */, + SettingsState.SYSTEM_PACKAGE_NAME); + } else { + // Clear a11y button targets list setting. A11yManagerService will end up + // adding all legacy enabled services that want the button to the list, so + // there's no need to keep tracking them. + secureSettings.insertSettingLocked( + Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT, + null, null /* tag */, false /* makeDefault */, + SettingsState.SYSTEM_PACKAGE_NAME); + } + secureSettings.deleteSettingLocked( + Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED); + currentVersion = 186; + } + // vXXX: Add new settings above this point. if (currentVersion != newVersion) { @@ -4590,7 +4672,7 @@ public class SettingsProvider extends ContentProvider { final boolean systemSet = SettingsState.isSystemPackage(getContext(), setting.getPackageName(), callingUid, userId); if (systemSet) { - settings.insertSettingLocked(name, setting.getValue(), + settings.insertSettingOverrideableByRestoreLocked(name, setting.getValue(), setting.getTag(), true, setting.getPackageName()); } else if (setting.getDefaultValue() != null && setting.isDefaultFromSystem()) { // We had a bug where changes by non-system packages were marked diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index 5b1b5305865e..db18213a3599 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -117,6 +117,8 @@ final class SettingsState { private static final String ATTR_NAMESPACE = "namespace"; private static final String ATTR_BANNED_HASH = "bannedHash"; + private static final String ATTR_PRESERVE_IN_RESTORE = "preserve_in_restore"; + /** * Non-binary value will be written in this attributes. */ @@ -388,15 +390,25 @@ final class SettingsState { // The settings provider must hold its lock when calling here. @GuardedBy("mLock") + public boolean insertSettingOverrideableByRestoreLocked(String name, String value, String tag, + boolean makeDefault, String packageName) { + return insertSettingLocked(name, value, tag, makeDefault, false, packageName, + /* overrideableByRestore */ true); + } + + // The settings provider must hold its lock when calling here. + @GuardedBy("mLock") public boolean insertSettingLocked(String name, String value, String tag, boolean makeDefault, String packageName) { - return insertSettingLocked(name, value, tag, makeDefault, false, packageName); + return insertSettingLocked(name, value, tag, makeDefault, false, packageName, + /* overrideableByRestore */ false); } // The settings provider must hold its lock when calling here. @GuardedBy("mLock") public boolean insertSettingLocked(String name, String value, String tag, - boolean makeDefault, boolean forceNonSystemPackage, String packageName) { + boolean makeDefault, boolean forceNonSystemPackage, String packageName, + boolean overrideableByRestore) { if (TextUtils.isEmpty(name)) { return false; } @@ -407,7 +419,8 @@ final class SettingsState { Setting newState; if (oldState != null) { - if (!oldState.update(value, makeDefault, packageName, tag, forceNonSystemPackage)) { + if (!oldState.update(value, makeDefault, packageName, tag, forceNonSystemPackage, + overrideableByRestore)) { return false; } newState = oldState; @@ -495,7 +508,8 @@ final class SettingsState { changedKeys.add(key); // key was added } else if (state.value != value) { oldValue = state.value; - state.update(value, false, packageName, null, true); + state.update(value, false, packageName, null, true, + /* overrideableByRestore */ false); changedKeys.add(key); // key was updated } else { // this key/value already exists, no change and no logging necessary @@ -797,7 +811,8 @@ final class SettingsState { writeSingleSetting(mVersion, serializer, setting.getId(), setting.getName(), setting.getValue(), setting.getDefaultValue(), setting.getPackageName(), - setting.getTag(), setting.isDefaultFromSystem()); + setting.getTag(), setting.isDefaultFromSystem(), + setting.isValuePreservedInRestore()); if (DEBUG_PERSISTENCE) { Slog.i(LOG_TAG, "[PERSISTED]" + setting.getName() + "=" @@ -886,7 +901,8 @@ final class SettingsState { static void writeSingleSetting(int version, XmlSerializer serializer, String id, String name, String value, String defaultValue, String packageName, - String tag, boolean defaultSysSet) throws IOException { + String tag, boolean defaultSysSet, boolean isValuePreservedInRestore) + throws IOException { if (id == null || isBinary(id) || name == null || isBinary(name) || packageName == null || isBinary(packageName)) { // This shouldn't happen. @@ -905,6 +921,9 @@ final class SettingsState { setValueAttribute(ATTR_TAG, ATTR_TAG_BASE64, version, serializer, tag); } + if (isValuePreservedInRestore) { + serializer.attribute(null, ATTR_PRESERVE_IN_RESTORE, Boolean.toString(true)); + } serializer.endTag(null, TAG_SETTING); } @@ -1041,6 +1060,10 @@ final class SettingsState { String packageName = parser.getAttributeValue(null, ATTR_PACKAGE); String defaultValue = getValueAttribute(parser, ATTR_DEFAULT_VALUE, ATTR_DEFAULT_VALUE_BASE64); + String isPreservedInRestoreString = parser.getAttributeValue(null, + ATTR_PRESERVE_IN_RESTORE); + boolean isPreservedInRestore = isPreservedInRestoreString != null + && Boolean.parseBoolean(isPreservedInRestoreString); String tag = null; boolean fromSystem = false; if (defaultValue != null) { @@ -1049,7 +1072,7 @@ final class SettingsState { tag = getValueAttribute(parser, ATTR_TAG, ATTR_TAG_BASE64); } mSettings.put(name, new Setting(name, value, defaultValue, packageName, tag, - fromSystem, id)); + fromSystem, id, isPreservedInRestore)); if (DEBUG_PERSISTENCE) { Slog.i(LOG_TAG, "[RESTORED] " + name + "=" + value); @@ -1133,6 +1156,8 @@ final class SettingsState { private String tag; // Whether the default is set by the system private boolean defaultFromSystem; + // Whether the value of this setting will be preserved when restore happens. + private boolean isValuePreservedInRestore; public Setting(Setting other) { name = other.name; @@ -1142,25 +1167,38 @@ final class SettingsState { id = other.id; defaultFromSystem = other.defaultFromSystem; tag = other.tag; + isValuePreservedInRestore = other.isValuePreservedInRestore; } public Setting(String name, String value, boolean makeDefault, String packageName, String tag) { this.name = name; - update(value, makeDefault, packageName, tag, false); + // overrideableByRestore = true as the first initialization isn't considered a + // modification. + update(value, makeDefault, packageName, tag, false, + /* overrideableByRestore */ true); } public Setting(String name, String value, String defaultValue, String packageName, String tag, boolean fromSystem, String id) { + this(name, value, defaultValue, packageName, tag, fromSystem, id, + /* isOverrideableByRestore */ false); + } + + Setting(String name, String value, String defaultValue, + String packageName, String tag, boolean fromSystem, String id, + boolean isValuePreservedInRestore) { mNextId = Math.max(mNextId, Long.parseLong(id) + 1); if (NULL_VALUE.equals(value)) { value = null; } - init(name, value, tag, defaultValue, packageName, fromSystem, id); + init(name, value, tag, defaultValue, packageName, fromSystem, id, + isValuePreservedInRestore); } private void init(String name, String value, String tag, String defaultValue, - String packageName, boolean fromSystem, String id) { + String packageName, boolean fromSystem, String id, + boolean isValuePreservedInRestore) { this.name = name; this.value = value; this.tag = tag; @@ -1168,6 +1206,7 @@ final class SettingsState { this.packageName = packageName; this.id = id; this.defaultFromSystem = fromSystem; + this.isValuePreservedInRestore = isValuePreservedInRestore; } public String getName() { @@ -1198,6 +1237,10 @@ final class SettingsState { return defaultFromSystem; } + public boolean isValuePreservedInRestore() { + return isValuePreservedInRestore; + } + public String getId() { return id; } @@ -1208,7 +1251,9 @@ final class SettingsState { /** @return whether the value changed */ public boolean reset() { - return update(this.defaultValue, false, packageName, null, true); + // overrideableByRestore = true as resetting to default value isn't considered a + // modification. + return update(this.defaultValue, false, packageName, null, true, true); } public boolean isTransient() { @@ -1220,7 +1265,7 @@ final class SettingsState { } public boolean update(String value, boolean setDefault, String packageName, String tag, - boolean forceNonSystemPackage) { + boolean forceNonSystemPackage, boolean overrideableByRestore) { if (NULL_VALUE.equals(value)) { value = null; } @@ -1253,17 +1298,22 @@ final class SettingsState { } } + // isValuePreservedInRestore shouldn't change back to false if it has been set to true. + boolean isPreserved = this.isValuePreservedInRestore || !overrideableByRestore; + // Is something gonna change? if (Objects.equals(value, this.value) && Objects.equals(defaultValue, this.defaultValue) && Objects.equals(packageName, this.packageName) && Objects.equals(tag, this.tag) - && defaultFromSystem == this.defaultFromSystem) { + && defaultFromSystem == this.defaultFromSystem + && isPreserved == this.isValuePreservedInRestore) { return false; } init(name, value, tag, defaultValue, packageName, defaultFromSystem, - String.valueOf(mNextId++)); + String.valueOf(mNextId++), isPreserved); + return true; } diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 72782258d237..6ea2c741cc35 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -369,7 +369,6 @@ public class SettingsBackupTest { Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME, Settings.Global.NETWORK_PREFERENCE, Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE, - Settings.Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS, Settings.Global.NETWORK_SCORER_APP, Settings.Global.NETWORK_SCORING_PROVISIONED, Settings.Global.NETWORK_SCORING_UI_ENABLED, @@ -523,8 +522,6 @@ public class SettingsBackupTest { Settings.Global.WIFI_BADGING_THRESHOLDS, Settings.Global.WIFI_BOUNCE_DELAY_OVERRIDE_MS, Settings.Global.WIFI_COUNTRY_CODE, - Settings.Global.WIFI_DATA_STALL_MIN_TX_BAD, - Settings.Global.WIFI_DATA_STALL_MIN_TX_SUCCESS_WITHOUT_RX, Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON, Settings.Global.WIFI_DISPLAY_ON, @@ -534,11 +531,6 @@ public class SettingsBackupTest { Settings.Global.WIFI_FRAMEWORK_SCAN_INTERVAL_MS, Settings.Global.WIFI_FREQUENCY_BAND, Settings.Global.WIFI_IDLE_MS, - Settings.Global.WIFI_IS_UNUSABLE_EVENT_METRICS_ENABLED, - Settings.Global.WIFI_LINK_SPEED_METRICS_ENABLED, - Settings.Global.WIFI_PNO_FREQUENCY_CULLING_ENABLED, - Settings.Global.WIFI_PNO_RECENCY_SORTING_ENABLED, - Settings.Global.WIFI_LINK_PROBING_ENABLED, Settings.Global.WIFI_MAX_DHCP_RETRY_COUNT, Settings.Global.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS, Settings.Global.WIFI_NETWORK_SHOW_RSSI, @@ -547,7 +539,6 @@ public class SettingsBackupTest { Settings.Global.WIFI_ON, Settings.Global.WIFI_P2P_DEVICE_NAME, Settings.Global.WIFI_P2P_PENDING_FACTORY_RESET, - Settings.Global.WIFI_RTT_BACKGROUND_EXEC_GAP_MS, Settings.Global.WIFI_SAVED_STATE, Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, Settings.Global.WIFI_SCAN_INTERVAL_WHEN_P2P_CONNECTED_MS, @@ -555,7 +546,6 @@ public class SettingsBackupTest { Settings.Global.WIFI_SCORE_PARAMS, Settings.Global.WIFI_SLEEP_POLICY, Settings.Global.WIFI_SUPPLICANT_SCAN_INTERVAL_MS, - Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED, Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED, Settings.Global.WIFI_WATCHDOG_ON, Settings.Global.WIMAX_NETWORKS_AVAILABLE_NOTIFICATION_ON, @@ -734,7 +724,8 @@ public class SettingsBackupTest { Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE, Settings.Secure.FACE_UNLOCK_RE_ENROLL, Settings.Secure.TAP_GESTURE, - Settings.Secure.WINDOW_MAGNIFICATION); + Settings.Secure.WINDOW_MAGNIFICATION, + Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER); @Test public void systemSettingsBackedUpOrBlacklisted() { diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java index 3f68554ffe87..b855d87fc214 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java @@ -46,6 +46,18 @@ public class SettingsStateTest extends AndroidTestCase { "\uD800ab\uDC00 " + // broken surrogate pairs "日本語"; + private static final String TEST_PACKAGE = "package"; + private static final String SETTING_NAME = "test_setting"; + + private final Object mLock = new Object(); + + private File mSettingsFile; + + @Override + protected void setUp() { + mSettingsFile = new File(getContext().getCacheDir(), "setting.xml"); + mSettingsFile.delete(); + } public void testIsBinary() { assertFalse(SettingsState.isBinary(" abc 日本語")); @@ -99,10 +111,10 @@ public class SettingsStateTest extends AndroidTestCase { checkWriteSingleSetting(serializer, CRAZY_STRING, null); SettingsState.writeSingleSetting( SettingsState.SETTINGS_VERSION_NEW_ENCODING, - serializer, null, "k", "v", null, "package", null, false); + serializer, null, "k", "v", null, "package", null, false, false); SettingsState.writeSingleSetting( SettingsState.SETTINGS_VERSION_NEW_ENCODING, - serializer, "1", "k", "v", null, null, null, false); + serializer, "1", "k", "v", null, null, null, false, false); } private void checkWriteSingleSetting(XmlSerializer serializer, String key, String value) @@ -115,7 +127,7 @@ public class SettingsStateTest extends AndroidTestCase { // Make sure the XML serializer won't crash. SettingsState.writeSingleSetting( SettingsState.SETTINGS_VERSION_NEW_ENCODING, - serializer, "1", key, value, null, "package", null, false); + serializer, "1", key, value, null, "package", null, false, false); } /** @@ -182,4 +194,57 @@ public class SettingsStateTest extends AndroidTestCase { assertEquals("p2", s.getPackageName()); } } + + public void testInitializeSetting_preserveFlagNotSet() { + SettingsState settingsWriter = getSettingStateObject(); + settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE); + settingsWriter.persistSyncLocked(); + + SettingsState settingsReader = getSettingStateObject(); + assertFalse(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore()); + } + + public void testModifySetting_preserveFlagSet() { + SettingsState settingsWriter = getSettingStateObject(); + settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE); + settingsWriter.insertSettingLocked(SETTING_NAME, "2", null, false, TEST_PACKAGE); + settingsWriter.persistSyncLocked(); + + SettingsState settingsReader = getSettingStateObject(); + assertTrue(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore()); + } + + public void testModifySettingOverrideableByRestore_preserveFlagNotSet() { + SettingsState settingsWriter = getSettingStateObject(); + settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE); + settingsWriter.insertSettingLocked(SETTING_NAME, "2", null, false, false, TEST_PACKAGE, + /* overrideableByRestore */ true); + settingsWriter.persistSyncLocked(); + + SettingsState settingsReader = getSettingStateObject(); + assertFalse(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore()); + } + + public void testModifySettingOverrideableByRestore_preserveFlagAlreadySet_flagValueUnchanged() { + SettingsState settingsWriter = getSettingStateObject(); + // Init the setting. + settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE); + // This modification will set isValuePreservedInRestore = true. + settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE); + // This modification shouldn't change the value of isValuePreservedInRestore since it's + // already been set to true. + settingsWriter.insertSettingLocked(SETTING_NAME, "2", null, false, false, TEST_PACKAGE, + /* overrideableByRestore */ true); + settingsWriter.persistSyncLocked(); + + SettingsState settingsReader = getSettingStateObject(); + assertTrue(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore()); + } + + private SettingsState getSettingStateObject() { + SettingsState settingsState = new SettingsState(getContext(), mLock, mSettingsFile, 1, + SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper()); + settingsState.setVersionLocked(SettingsState.SETTINGS_VERSION_NEW_ENCODING); + return settingsState; + } } diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 2a1e74e18fb7..0bcadce7a9c6 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -70,7 +70,7 @@ <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <uses-permission android:name="android.permission.OVERRIDE_WIFI_CONFIG" /> <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" /> - <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" /> + <uses-permission android:name="android.permission.OBSERVE_NETWORK_POLICY" /> <uses-permission android:name="android.permission.NETWORK_SETTINGS" /> <uses-permission android:name="android.permission.TETHER_PRIVILEGED" /> <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" /> @@ -211,6 +211,7 @@ <!-- accessibility --> <uses-permission android:name="android.permission.MODIFY_ACCESSIBILITY_DATA" /> + <uses-permission android:name="android.permission.MANAGE_ACCESSIBILITY" /> <!-- to control accessibility volume --> <uses-permission android:name="android.permission.CHANGE_ACCESSIBILITY_VOLUME" /> @@ -307,7 +308,8 @@ </receiver> <activity android:name=".screenrecord.ScreenRecordDialog" - android:theme="@style/ScreenRecord" /> + android:theme="@style/ScreenRecord" + android:excludeFromRecents="true" /> <service android:name=".screenrecord.RecordingService" /> <receiver android:name=".SysuiRestartReceiver" @@ -636,6 +638,23 @@ </intent-filter> </activity> + <activity android:name=".controls.management.ControlsProviderSelectorActivity" + android:label="Controls Providers" + android:theme="@style/Theme.SystemUI" + android:exported="true" + android:excludeFromRecents="true" + android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden" + android:visibleToInstantApps="true"> + </activity> + + <activity android:name=".controls.management.ControlsFavoritingActivity" + android:parentActivityName=".controls.management.ControlsProviderSelectorActivity" + android:theme="@style/Theme.SystemUI" + android:excludeFromRecents="true" + android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden" + android:visibleToInstantApps="true"> + </activity> + <!-- Doze with notifications, run in main sysui process for every user --> <service android:name=".doze.DozeService" diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java index 8db0d02548b0..02c4c5eff26e 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java @@ -46,6 +46,8 @@ public interface NotificationSwipeActionHelper { */ public void snooze(StatusBarNotification sbn, SnoozeOption snoozeOption); + public void snooze(StatusBarNotification sbn, int hours); + public float getMinDismissVelocity(); public boolean isDismissGesture(MotionEvent ev); diff --git a/packages/SystemUI/res/drawable/ic_add_to_home.xml b/packages/SystemUI/res/drawable/ic_add_to_home.xml new file mode 100644 index 000000000000..2b40c627be1d --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_add_to_home.xml @@ -0,0 +1,26 @@ +<!-- + 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 + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M18,1.01L8,1c-1.1,0 -2,0.9 -2,2v3h2V5h10v14H8v-1H6v3c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-1.99 -2,-1.99zM10,15h2V8H5v2h3.59L3,15.59 4.41,17 10,11.41V15z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_demote_conversation.xml b/packages/SystemUI/res/drawable/ic_demote_conversation.xml new file mode 100644 index 000000000000..5a881605f800 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_demote_conversation.xml @@ -0,0 +1,25 @@ +<!-- + 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 + --> + +<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:fillColor="@android:color/white" + android:pathData="M20,2L4.83,2l2,2L20,4v12h-1.17l1.87,1.87c0.75,-0.29 1.3,-1.02 1.3,-1.87L22,4c0,-1.1 -0.9,-2 -2,-2zM6,12h2v2L6,14zM18,11L18,9h-6.17l2,2zM18,6h-8v1.17l0.83,0.83L18,8zM0.69,3.51l1.32,1.32L2,22l4,-4h9.17l5.31,5.31 1.41,-1.41L2.1,2.1 0.69,3.51zM6,16h-0.83l-0.59,0.59 -0.58,0.58L4,6.83l2,2L6,11h2.17l5,5L6,16z"/> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_qs_screenrecord.xml b/packages/SystemUI/res/drawable/ic_qs_screenrecord.xml new file mode 100644 index 000000000000..687c9c417fa6 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_qs_screenrecord.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="#FF000000" + android:pathData="M18,10.48L18,6c0,-1.1 -0.9,-2 -2,-2L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2v-4.48l4,3.98v-11l-4,3.98zM16,9.69L16,18L4,18L4,6h12v3.69z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_snooze.xml b/packages/SystemUI/res/drawable/ic_snooze.xml index b0b03a99f4a5..f4c074d3fc03 100644 --- a/packages/SystemUI/res/drawable/ic_snooze.xml +++ b/packages/SystemUI/res/drawable/ic_snooze.xml @@ -1,12 +1,24 @@ +<!-- + Copyright (C) 2017 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 + --> <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="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z" - android:fillColor="#757575"/> - <path - android:pathData="M12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z" - android:fillColor="#757575"/> + android:fillColor="@android:color/white" + android:pathData="M9,11h3.63L9,15.2L9,17h6v-2h-3.63L15,10.8L15,9L9,9v2zM16.056,3.346l1.282,-1.535 4.607,3.85 -1.28,1.54zM3.336,7.19l-1.28,-1.536L6.662,1.81l1.28,1.536zM12,6c3.86,0 7,3.14 7,7s-3.14,7 -7,7 -7,-3.14 -7,-7 3.14,-7 7,-7m0,-2c-4.97,0 -9,4.03 -9,9s4.03,9 9,9 9,-4.03 9,-9 -4.03,-9 -9,-9z"/> </vector> diff --git a/packages/SystemUI/res/drawable/ic_star.xml b/packages/SystemUI/res/drawable/ic_star.xml new file mode 100644 index 000000000000..4a731b35b423 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_star.xml @@ -0,0 +1,25 @@ +<!-- + 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 + --> + +<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:fillColor="@android:color/white" + android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27z"/> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_star_border.xml b/packages/SystemUI/res/drawable/ic_star_border.xml new file mode 100644 index 000000000000..9ede40be3b7b --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_star_border.xml @@ -0,0 +1,25 @@ +<!-- + 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 + --> + +<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:fillColor="@android:color/white" + android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/app_item.xml b/packages/SystemUI/res/layout/app_item.xml new file mode 100644 index 000000000000..83e788731442 --- /dev/null +++ b/packages/SystemUI/res/layout/app_item.xml @@ -0,0 +1,70 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2019 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?android:attr/selectableItemBackground" + android:gravity="center_vertical" + android:minHeight="?android:attr/listPreferredItemHeightSmall" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> + + <LinearLayout + android:id="@+id/icon_frame" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="start|center_vertical" + android:minWidth="56dp" + android:orientation="horizontal" + android:paddingEnd="8dp" + android:paddingTop="4dp" + android:paddingBottom="4dp"> + <ImageView + android:id="@android:id/icon" + android:layout_width="@dimen/app_icon_size" + android:layout_height="@dimen/app_icon_size"/> + </LinearLayout> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:orientation="vertical" + android:paddingTop="16dp" + android:paddingBottom="16dp"> + + <TextView + android:id="@android:id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ellipsize="marquee" + android:fadingEdge="horizontal" + android:singleLine="true" + android:textAppearance="?android:attr/textAppearanceListItem"/> + + </LinearLayout> + + <LinearLayout + android:id="@android:id/widget_frame" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:gravity="center_vertical|end" + android:minWidth="64dp" + android:orientation="vertical"/> + +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/control_item.xml b/packages/SystemUI/res/layout/control_item.xml new file mode 100644 index 000000000000..85701aaca41d --- /dev/null +++ b/packages/SystemUI/res/layout/control_item.xml @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<androidx.constraintlayout.widget.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="100dp" + android:padding="15dp" + android:clickable="true" + android:focusable="true"> + + <ImageView + android:id="@+id/icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <TextView + android:id="@+id/status" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="12sp" + android:textColor="?android:attr/textColorPrimary" + android:fontFamily="@*android:string/config_bodyFontFamily" + android:paddingLeft="3dp" + app:layout_constraintBottom_toBottomOf="@+id/icon" + app:layout_constraintStart_toEndOf="@+id/icon" /> + + <TextView + android:id="@+id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="18sp" + android:textColor="?android:attr/textColorPrimary" + android:fontFamily="@*android:string/config_headlineFontFamily" + app:layout_constraintBottom_toTopOf="@+id/subtitle" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/icon" /> + + <TextView + android:id="@+id/subtitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="16sp" + android:textColor="?android:attr/textColorSecondary" + android:fontFamily="@*android:string/config_headlineFontFamily" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" /> + + <CheckBox + android:id="@+id/favorite" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent"/> +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml b/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml new file mode 100644 index 000000000000..50aa212c94a6 --- /dev/null +++ b/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2008 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. +--> + +<!-- RelativeLayouts have an issue enforcing minimum heights, so just + work around this for now with LinearLayouts. --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:paddingTop="@dimen/global_actions_grid_item_vertical_margin" + android:paddingBottom="@dimen/global_actions_grid_item_vertical_margin" + android:paddingLeft="@dimen/global_actions_grid_item_side_margin" + android:paddingRight="@dimen/global_actions_grid_item_side_margin" + android:layout_marginRight="3dp" + android:layout_marginLeft="3dp" + android:background="@drawable/rounded_bg_full"> + <LinearLayout + android:layout_width="@dimen/global_actions_grid_item_width" + android:layout_height="@dimen/global_actions_grid_item_height" + android:gravity="top|center_horizontal" + android:orientation="vertical"> + <ImageView + android:id="@*android:id/icon" + android:layout_width="@dimen/global_actions_grid_item_icon_width" + android:layout_height="@dimen/global_actions_grid_item_icon_height" + android:layout_marginTop="@dimen/global_actions_grid_item_icon_top_margin" + android:layout_marginBottom="@dimen/global_actions_grid_item_icon_bottom_margin" + android:layout_marginLeft="@dimen/global_actions_grid_item_icon_side_margin" + android:layout_marginRight="@dimen/global_actions_grid_item_icon_side_margin" + android:scaleType="centerInside" + android:tint="@color/global_actions_text" /> + + <TextView + android:id="@*android:id/message" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ellipsize="marquee" + android:marqueeRepeatLimit="marquee_forever" + android:singleLine="true" + android:gravity="center" + android:textSize="12dp" + android:textColor="@color/global_actions_text" + android:textAppearance="?android:attr/textAppearanceSmall" /> + + <TextView + android:visibility="gone" + android:id="@*android:id/status" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center" + android:textColor="@color/global_actions_text" + android:textAppearance="?android:attr/textAppearanceSmall" /> + </LinearLayout> +</LinearLayout> diff --git a/packages/SystemUI/res/layout/global_actions_grid_v2.xml b/packages/SystemUI/res/layout/global_actions_grid_v2.xml new file mode 100644 index 000000000000..4cfb47e3c642 --- /dev/null +++ b/packages/SystemUI/res/layout/global_actions_grid_v2.xml @@ -0,0 +1,130 @@ +<?xml version="1.0" encoding="utf-8"?> +<ScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/global_actions_grid_root" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:clipChildren="false" + android:clipToPadding="false" + android:layout_marginBottom="@dimen/global_actions_grid_container_negative_shadow_offset"> + + <com.android.systemui.globalactions.GlobalActionsFlatLayout + android:id="@id/global_actions_view" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:theme="@style/qs_theme" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + android:gravity="top | center_horizontal" + android:clipChildren="false" + android:clipToPadding="false" + android:paddingBottom="@dimen/global_actions_grid_container_shadow_offset" + android:layout_marginTop="@dimen/global_actions_top_margin" + android:layout_marginBottom="@dimen/global_actions_grid_container_negative_shadow_offset"> + <LinearLayout + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layoutDirection="ltr" + android:clipChildren="false" + android:clipToPadding="false" + android:layout_marginBottom="@dimen/global_actions_grid_container_bottom_margin"> + <!-- For separated items--> + <LinearLayout + android:id="@+id/separated_button" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_marginLeft="@dimen/global_actions_grid_side_margin" + android:layout_marginRight="@dimen/global_actions_grid_side_margin" + android:paddingLeft="@dimen/global_actions_grid_horizontal_padding" + android:paddingRight="@dimen/global_actions_grid_horizontal_padding" + android:paddingTop="@dimen/global_actions_grid_vertical_padding" + android:paddingBottom="@dimen/global_actions_grid_vertical_padding" + android:orientation="vertical" + android:gravity="center" + android:translationZ="@dimen/global_actions_translate" + /> + <!-- Grid of action items --> + <com.android.systemui.globalactions.ListGridLayout + android:id="@android:id/list" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + android:gravity="right" + android:layout_marginRight="@dimen/global_actions_grid_side_margin" + android:translationZ="@dimen/global_actions_translate" + android:paddingLeft="@dimen/global_actions_grid_horizontal_padding" + android:paddingRight="@dimen/global_actions_grid_horizontal_padding" + android:paddingTop="@dimen/global_actions_grid_vertical_padding" + android:paddingBottom="@dimen/global_actions_grid_vertical_padding" + > + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:layoutDirection="locale" + /> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:layoutDirection="locale" + /> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:layoutDirection="locale" + /> + </com.android.systemui.globalactions.ListGridLayout> + </LinearLayout> + </com.android.systemui.globalactions.GlobalActionsFlatLayout> + + <LinearLayout + android:id="@+id/global_actions_panel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toBottomOf="@id/global_actions_view"> + + <FrameLayout + android:translationY="@dimen/global_actions_plugin_offset" + android:id="@+id/global_actions_panel_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + </LinearLayout> + + <LinearLayout + android:translationY="@dimen/global_actions_plugin_offset" + android:id="@+id/global_actions_controls" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toBottomOf="@id/global_actions_panel"> + <TextView + android:text="Home" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:singleLine="true" + android:gravity="center" + android:textSize="25dp" + android:textColor="?android:attr/textColorPrimary" + android:fontFamily="@*android:string/config_headlineFontFamily" /> + <LinearLayout + android:id="@+id/global_actions_controls_list" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" /> + </LinearLayout> + </androidx.constraintlayout.widget.ConstraintLayout> +</ScrollView> diff --git a/packages/SystemUI/res/layout/global_screenshot_action_chip.xml b/packages/SystemUI/res/layout/global_screenshot_action_chip.xml index 6b424002d7ff..366abaa1366d 100644 --- a/packages/SystemUI/res/layout/global_screenshot_action_chip.xml +++ b/packages/SystemUI/res/layout/global_screenshot_action_chip.xml @@ -14,12 +14,27 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<TextView xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/global_screenshot_action_chip" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginHorizontal="@dimen/screenshot_action_chip_margin_horizontal" - android:paddingVertical="@dimen/screenshot_action_chip_padding_vertical" - android:paddingHorizontal="@dimen/screenshot_action_chip_padding_horizontal" - android:background="@drawable/action_chip_background" - android:textColor="@color/global_screenshot_button_text"/> +<com.android.systemui.screenshot.ScreenshotActionChip + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/global_screenshot_action_chip" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginHorizontal="@dimen/screenshot_action_chip_margin_horizontal" + android:layout_gravity="center" + android:paddingVertical="@dimen/screenshot_action_chip_padding_vertical" + android:background="@drawable/action_chip_background" + android:gravity="center"> + <ImageView + android:id="@+id/screenshot_action_chip_icon" + android:layout_width="@dimen/screenshot_action_chip_icon_size" + android:layout_height="@dimen/screenshot_action_chip_icon_size" + android:layout_marginStart="@dimen/screenshot_action_chip_padding_start" + android:layout_marginEnd="@dimen/screenshot_action_chip_padding_middle"/> + <TextView + android:id="@+id/screenshot_action_chip_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/screenshot_action_chip_padding_end" + android:textSize="@dimen/screenshot_action_chip_text_size" + android:textColor="@color/global_screenshot_button_text"/> +</com.android.systemui.screenshot.ScreenshotActionChip> diff --git a/packages/SystemUI/res/layout/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml new file mode 100644 index 000000000000..8749b1a0bcc8 --- /dev/null +++ b/packages/SystemUI/res/layout/notification_conversation_info.xml @@ -0,0 +1,254 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> + +<com.android.systemui.statusbar.notification.row.NotificationConversationInfo + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/notification_guts" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:clickable="true" + android:clipChildren="false" + android:clipToPadding="true" + android:orientation="vertical" + android:background="@color/notification_material_background_color" + android:paddingStart="@*android:dimen/notification_content_margin_start"> + + <!-- Package Info --> + <RelativeLayout + android:id="@+id/header" + android:layout_width="match_parent" + android:layout_height="@dimen/notification_guts_conversation_header_height" + android:gravity="center_vertical" + android:clipChildren="false" + android:clipToPadding="false"> + <ImageView + android:id="@+id/conversation_icon" + android:layout_width="@dimen/notification_guts_conversation_icon_size" + android:layout_height="@dimen/notification_guts_conversation_icon_size" + android:layout_centerVertical="true" + android:layout_alignParentStart="true" + android:layout_marginEnd="6dp" /> + <LinearLayout + android:id="@+id/names" + android:orientation="vertical" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:minHeight="@dimen/notification_guts_conversation_icon_size" + android:layout_centerVertical="true" + android:gravity="center_vertical" + android:layout_toEndOf="@id/conversation_icon"> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="start" + android:orientation="horizontal"> + <TextView + android:id="@+id/parent_channel_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + style="@style/TextAppearance.NotificationImportanceChannel"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + style="@style/TextAppearance.NotificationImportanceHeader" + android:layout_marginStart="2dp" + android:layout_marginEnd="2dp" + android:text="@*android:string/notification_header_divider_symbol" /> + <TextView + android:id="@+id/name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + style="@style/TextAppearance.NotificationImportanceChannel"/> + </LinearLayout> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="start" + android:orientation="horizontal"> + <TextView + android:id="@+id/pkg_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="@style/TextAppearance.NotificationImportanceChannelGroup" + android:ellipsize="end" + android:maxLines="1"/> + <TextView + android:id="@+id/group_divider" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + style="@style/TextAppearance.NotificationImportanceHeader" + android:layout_marginStart="2dp" + android:layout_marginEnd="2dp" + android:text="@*android:string/notification_header_divider_symbol" /> + <TextView + android:id="@+id/group_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + style="@style/TextAppearance.NotificationImportanceChannel"/> + </LinearLayout> + + </LinearLayout> + + <TextView + android:id="@+id/pkg_divider" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + style="@style/TextAppearance.NotificationImportanceHeader" + android:layout_marginStart="2dp" + android:layout_marginEnd="2dp" + android:layout_toEndOf="@id/name" + android:text="@*android:string/notification_header_divider_symbol" /> + <TextView + android:id="@+id/delegate_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + style="@style/TextAppearance.NotificationImportanceHeader" + android:layout_marginStart="2dp" + android:layout_marginEnd="2dp" + android:ellipsize="end" + android:text="@string/notification_delegate_header" + android:layout_toEndOf="@id/pkg_divider" + android:maxLines="1" /> + + <!-- end aligned fields --> + <ImageButton + android:id="@+id/demote" + android:layout_width="@dimen/notification_importance_toggle_size" + android:layout_height="@dimen/notification_importance_toggle_size" + android:layout_centerVertical="true" + android:background="@drawable/ripple_drawable" + android:contentDescription="@string/demote" + android:src="@drawable/ic_demote_conversation" + android:layout_toStartOf="@id/app_settings" + android:tint="@color/notification_guts_link_icon_tint"/> + <!-- Optional link to app. Only appears if the channel is not disabled and the app +asked for it --> + <ImageButton + android:id="@+id/app_settings" + android:layout_width="@dimen/notification_importance_toggle_size" + android:layout_height="@dimen/notification_importance_toggle_size" + android:layout_centerVertical="true" + android:visibility="gone" + android:background="@drawable/ripple_drawable" + android:contentDescription="@string/notification_app_settings" + android:src="@drawable/ic_info" + android:layout_toStartOf="@id/info" + android:tint="@color/notification_guts_link_icon_tint"/> + <ImageButton + android:id="@+id/info" + android:layout_width="@dimen/notification_importance_toggle_size" + android:layout_height="@dimen/notification_importance_toggle_size" + android:layout_centerVertical="true" + android:background="@drawable/ripple_drawable" + android:contentDescription="@string/notification_more_settings" + android:src="@drawable/ic_settings" + android:layout_alignParentEnd="true" + android:tint="@color/notification_guts_link_icon_tint"/> + </RelativeLayout> + + <LinearLayout + android:id="@+id/actions" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginEnd="@*android:dimen/notification_content_margin_end" + android:clipChildren="false" + android:clipToPadding="false" + android:orientation="vertical"> + + <View + android:layout_width="match_parent" + android:layout_height="0.5dp" + android:background="@color/GM2_grey_300" /> + <Button + android:id="@+id/bubble" + android:layout_height="@dimen/notification_guts_conversation_action_height" + android:layout_width="match_parent" + style="?android:attr/borderlessButtonStyle" + android:text="@string/notification_conversation_favorite" + android:gravity="left|center_vertical" + android:drawableStart="@drawable/ic_create_bubble" + android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start" + android:drawableTint="@color/notification_guts_link_icon_tint"/> + + <View + android:layout_width="match_parent" + android:layout_height="0.5dp" + android:background="@color/GM2_grey_300" /> + <Button + android:id="@+id/home" + android:layout_height="@dimen/notification_guts_conversation_action_height" + android:layout_width="match_parent" + style="?android:attr/borderlessButtonStyle" + android:text="@string/notification_conversation_home_screen" + android:gravity="left|center_vertical" + android:drawableStart="@drawable/ic_add_to_home" + android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start" + android:drawableTint="@color/notification_guts_link_icon_tint"/> + + <View + android:layout_width="match_parent" + android:layout_height="0.5dp" + android:background="@color/GM2_grey_300" /> + <Button + android:id="@+id/fave" + android:layout_height="@dimen/notification_guts_conversation_action_height" + android:layout_width="match_parent" + style="?android:attr/borderlessButtonStyle" + android:gravity="left|center_vertical" + android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start" + android:drawableTint="@color/notification_guts_link_icon_tint"/> + + <View + android:layout_width="match_parent" + android:layout_height="0.5dp" + android:background="@color/GM2_grey_300" /> + <Button + android:id="@+id/snooze" + android:layout_height="@dimen/notification_guts_conversation_action_height" + android:layout_width="match_parent" + style="?android:attr/borderlessButtonStyle" + android:text="@string/notification_menu_snooze_action" + android:gravity="left|center_vertical" + android:drawableStart="@drawable/ic_snooze" + android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start" + android:drawableTint="@color/notification_guts_link_icon_tint"/> + + <View + android:layout_width="match_parent" + android:layout_height="0.5dp" + android:background="@color/GM2_grey_300" /> + <Button + android:id="@+id/mute" + android:layout_height="@dimen/notification_guts_conversation_action_height" + android:layout_width="match_parent" + style="?android:attr/borderlessButtonStyle" + android:text="@string/notification_conversation_mute" + android:gravity="left|center_vertical" + android:drawableStart="@drawable/ic_notifications_silence" + android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start" + android:drawableTint="@color/notification_guts_link_icon_tint"/> + + </LinearLayout> + +</com.android.systemui.statusbar.notification.row.NotificationConversationInfo> diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml deleted file mode 100644 index 3d63b7d19c4f..000000000000 --- a/packages/SystemUI/res/layout/screen_record_dialog.xml +++ /dev/null @@ -1,36 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:clipChildren="false" - android:clipToPadding="false" - android:gravity="top" - android:orientation="vertical" - android:padding="@dimen/global_actions_padding" - android:background="@drawable/rounded_bg_full"> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical"> - <CheckBox - android:id="@+id/checkbox_mic" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceMedium" - android:text="@string/screenrecord_mic_label"/> - <CheckBox - android:id="@+id/checkbox_taps" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceMedium" - android:text="@string/screenrecord_taps_label"/> - <Button - android:id="@+id/record_button" - android:layout_width="match_parent" - android:layout_height="50dp" - android:text="@string/screenrecord_start_label" - /> - </LinearLayout> - -</LinearLayout> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 640f31bc9fe8..896315725644 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -109,7 +109,7 @@ <!-- The default tiles to display in QuickSettings --> <string name="quick_settings_tiles_default" translatable="false"> - wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast + wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast,screenrecord </string> <!-- The minimum number of tiles to display in QuickSettings --> @@ -117,7 +117,7 @@ <!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" --> <string name="quick_settings_tiles_stock" translatable="false"> - wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,controls + wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,controls,screenrecord </string> <!-- The tiles to display in QuickSettings --> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 5a1151eb9f6e..53df02588f11 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -213,6 +213,11 @@ <!-- The horizontal space around the buttons in the inline settings --> <dimen name="notification_guts_button_horizontal_spacing">8dp</dimen> + <dimen name="notification_guts_conversation_header_height">84dp</dimen> + <dimen name="notification_guts_conversation_icon_size">52dp</dimen> + <dimen name="notification_guts_conversation_action_height">56dp</dimen> + <dimen name="notification_guts_conversation_action_text_padding_start">32dp</dimen> + <!-- The height of the header in inline settings --> <dimen name="notification_guts_header_height">24dp</dimen> @@ -291,12 +296,17 @@ <dimen name="global_screenshot_legacy_bg_padding">20dp</dimen> <dimen name="global_screenshot_bg_padding">20dp</dimen> <dimen name="screenshot_action_container_corner_radius">10dp</dimen> - <dimen name="screenshot_action_container_padding">20dp</dimen> + <dimen name="screenshot_action_container_padding">10dp</dimen> <!-- Radius of the chip background on global screenshot actions --> <dimen name="screenshot_button_corner_radius">20dp</dimen> - <dimen name="screenshot_action_chip_margin_horizontal">10dp</dimen> + <dimen name="screenshot_action_chip_margin_horizontal">4dp</dimen> <dimen name="screenshot_action_chip_padding_vertical">10dp</dimen> - <dimen name="screenshot_action_chip_padding_horizontal">15dp</dimen> + <dimen name="screenshot_action_chip_icon_size">20dp</dimen> + <dimen name="screenshot_action_chip_padding_start">4dp</dimen> + <!-- Padding between icon and text --> + <dimen name="screenshot_action_chip_padding_middle">8dp</dimen> + <dimen name="screenshot_action_chip_padding_end">12dp</dimen> + <dimen name="screenshot_action_chip_text_size">14sp</dimen> <!-- The width of the view containing navigation buttons --> @@ -951,6 +961,9 @@ <dimen name="cell_overlay_padding">18dp</dimen> <!-- Global actions power menu --> + <dimen name="global_actions_top_margin">12dp</dimen> + <dimen name="global_actions_plugin_offset">-145dp</dimen> + <dimen name="global_actions_panel_width">120dp</dimen> <dimen name="global_actions_padding">12dp</dimen> <dimen name="global_actions_translate">9dp</dimen> @@ -1152,4 +1165,5 @@ <dimen name="magnifier_up_down_controls_width">45dp</dimen> <dimen name="magnifier_up_down_controls_height">40dp</dimen> + <dimen name="app_icon_size">32dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 4f532b7b751d..91299386d68a 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -928,6 +928,13 @@ <!-- QuickSettings: NFC (on) [CHAR LIMIT=NONE] --> <string name="quick_settings_nfc_on">NFC is enabled</string> + <!-- QuickSettings: Screen record tile [CHAR LIMIT=NONE] --> + <string name="quick_settings_screen_record_label">Screen Record</string> + <!-- QuickSettings: Text to prompt the user to begin a new recording [CHAR LIMIT=20] --> + <string name="quick_settings_screen_record_start">Start</string> + <!-- QuickSettings: Text to prompt the user to stop an ongoing recording [CHAR LIMIT=20] --> + <string name="quick_settings_screen_record_stop">Stop</string> + <!-- Recents: Text that shows above the navigation bar after launching a few apps. [CHAR LIMIT=NONE] --> <string name="recents_swipe_up_onboarding">Swipe up to switch apps</string> <!-- Recents: Text that shows above the navigation bar after launching several apps. [CHAR LIMIT=NONE] --> @@ -1791,6 +1798,29 @@ <string name="notification_done">Done</string> <!-- Notification: inline controls: undo block button --> <string name="inline_undo">Undo</string> + <!-- Notification: Conversation: control panel, label for button that demotes notification from conversation to normal notification --> + <string name="demote">Mark this notification as not a conversation</string> + + <!-- [CHAR LIMIT=100] Mark this conversation as a favorite --> + <string name="notification_conversation_favorite">Favorite</string> + + <!-- [CHAR LIMIT=100] Unmark this conversation as a favorite --> + <string name="notification_conversation_unfavorite">Unfavorite</string> + + <!-- [CHAR LIMIT=100] Mute this conversation --> + <string name="notification_conversation_mute">Mute</string> + + <!-- [CHAR LIMIT=100] Umute this conversation --> + <string name="notification_conversation_unmute">Unmute</string> + + <!-- [CHAR LIMIT=100] Show notification as bubble --> + <string name="notification_conversation_bubble">Show as bubble</string> + + <!-- [CHAR LIMIT=100] Turn off bubbles for notification --> + <string name="notification_conversation_unbubble">Turn off bubbles</string> + + <!-- [CHAR LIMIT=100] Add this conversation to home screen --> + <string name="notification_conversation_home_screen">Add to home screen</string> <!-- Notification: Menu row: Content description for menu items. [CHAR LIMIT=NONE] --> <string name="notification_menu_accessibility"><xliff:g id="app_name" example="YouTube">%1$s</xliff:g> <xliff:g id="menu_description" example="notification controls">%2$s</xliff:g></string> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index df0dc467f87e..e475ef1d9761 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -387,8 +387,8 @@ public class KeyguardClockSwitch extends RelativeLayout { * Refresh the time of the clock, due to either time tick broadcast or doze time tick alarm. */ public void refresh() { - mClockView.refresh(); - mClockViewBold.refresh(); + mClockView.refreshTime(); + mClockViewBold.refreshTime(); if (mClockPlugin != null) { mClockPlugin.onTimeTick(); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index a58e3d78bb08..65fc215f7505 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -31,8 +31,8 @@ import static android.os.BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE; import static android.os.BatteryManager.EXTRA_PLUGGED; import static android.os.BatteryManager.EXTRA_STATUS; import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE; +import static android.telephony.TelephonyManager.MODEM_COUNT_DUAL_MODEM; -import static com.android.internal.telephony.PhoneConstants.MAX_PHONE_COUNT_DUAL_SIM; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT; @@ -92,7 +92,6 @@ import android.util.Log; import android.util.SparseBooleanArray; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.TelephonyIntents; import com.android.internal.widget.LockPatternUtils; import com.android.settingslib.WirelessUtils; @@ -446,7 +445,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab */ public List<SubscriptionInfo> getFilteredSubscriptionInfo(boolean forceReload) { List<SubscriptionInfo> subscriptions = getSubscriptionInfo(false); - if (subscriptions.size() == MAX_PHONE_COUNT_DUAL_SIM) { + if (subscriptions.size() == MODEM_COUNT_DUAL_MODEM) { SubscriptionInfo info1 = subscriptions.get(0); SubscriptionInfo info2 = subscriptions.get(1); if (info1.getGroupUuid() != null && info1.getGroupUuid().equals(info2.getGroupUuid())) { @@ -1074,7 +1073,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mHandler.sendEmptyMessage(MSG_AIRPLANE_MODE_CHANGED); } else if (Intent.ACTION_SERVICE_STATE.equals(action)) { ServiceState serviceState = ServiceState.newFromBundle(intent.getExtras()); - int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, + int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, SubscriptionManager.INVALID_SUBSCRIPTION_ID); if (DEBUG) { Log.v(TAG, "action " + action + " serviceState=" + serviceState + " subId=" @@ -1236,8 +1235,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab throw new IllegalArgumentException("only handles intent ACTION_SIM_STATE_CHANGED"); } String stateExtra = intent.getStringExtra(Intent.EXTRA_SIM_STATE); - int slotId = intent.getIntExtra(PhoneConstants.PHONE_KEY, 0); - int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, + int slotId = intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 0); + int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, SubscriptionManager.INVALID_SUBSCRIPTION_ID); if (Intent.SIM_STATE_ABSENT.equals(stateExtra)) { final String absentReason = intent diff --git a/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java index eba24004e387..99e122ef74e9 100644 --- a/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java +++ b/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java @@ -188,7 +188,7 @@ public class AnalogClockController implements ClockPlugin { public void onTimeTick() { mAnalogClock.onTimeChanged(); mBigClockView.onTimeChanged(); - mLockClock.refresh(); + mLockClock.refreshTime(); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java index 3a2fbe5a9653..fac923c01af5 100644 --- a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java +++ b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java @@ -195,7 +195,7 @@ public class BubbleClockController implements ClockPlugin { public void onTimeTick() { mAnalogClock.onTimeChanged(); mView.onTimeChanged(); - mLockClock.refresh(); + mLockClock.refreshTime(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index bbe972dea11f..d1495913d95f 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -56,6 +56,7 @@ import com.android.systemui.power.EnhancedEstimates; import com.android.systemui.power.PowerUI; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.Recents; +import com.android.systemui.screenrecord.RecordingController; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.DevicePolicyManagerWrapper; @@ -323,6 +324,7 @@ public class Dependency { @Inject Lazy<DisplayWindowController> mDisplayWindowController; @Inject Lazy<SystemWindows> mSystemWindows; @Inject Lazy<DisplayImeController> mDisplayImeController; + @Inject Lazy<RecordingController> mRecordingController; @Inject public Dependency() { @@ -519,6 +521,8 @@ public class Dependency { // Dependency problem. mProviders.put(AutoHideController.class, mAutoHideController::get); + mProviders.put(RecordingController.class, mRecordingController::get); + sDependency = this; } diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java index 8105faa23e89..eab970626bf1 100644 --- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java +++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java @@ -27,9 +27,9 @@ import android.util.Log; import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.collection.NotifCollection; -import com.android.systemui.statusbar.notification.collection.NotifCollectionListener; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import javax.inject.Inject; import javax.inject.Singleton; @@ -49,7 +49,7 @@ public class ForegroundServiceNotificationListener { public ForegroundServiceNotificationListener(Context context, ForegroundServiceController foregroundServiceController, NotificationEntryManager notificationEntryManager, - NotifCollection notifCollection) { + NotifPipeline notifPipeline) { mContext = context; mForegroundServiceController = foregroundServiceController; @@ -77,7 +77,7 @@ public class ForegroundServiceNotificationListener { }); mEntryManager.addNotificationLifetimeExtender(new ForegroundServiceLifetimeExtender()); - notifCollection.addCollectionListener(new NotifCollectionListener() { + notifPipeline.addCollectionListener(new NotifCollectionListener() { @Override public void onEntryAdded(NotificationEntry entry) { addNotification(entry, entry.getImportance()); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java index 018b6318575a..d99607fd6236 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java @@ -10,6 +10,8 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.systemui.R; +import javax.inject.Inject; + /** * Activity for showing aged out bubbles. * Must be public to be accessible to androidx...AppComponentFactory @@ -17,6 +19,12 @@ import com.android.systemui.R; public class BubbleOverflowActivity extends Activity { private RecyclerView mRecyclerView; private int mMaxBubbles; + private BubbleController mBubbleController; + + @Inject + public BubbleOverflowActivity(BubbleController controller) { + mBubbleController = controller; + } @Override public void onCreate(Bundle savedInstanceState) { @@ -64,6 +72,6 @@ public class BubbleOverflowActivity extends Activity { } public void onDestroy() { - super.onStop(); + super.onDestroy(); } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 898768311031..15c1c5524a74 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -73,7 +73,6 @@ import com.android.systemui.R; import com.android.systemui.bubbles.animation.ExpandedAnimationController; import com.android.systemui.bubbles.animation.PhysicsAnimationLayout; import com.android.systemui.bubbles.animation.StackAnimationController; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -716,22 +715,6 @@ public class BubbleStackView extends FrameLayout { return mExpandedBubble; } - /** - * Sets the bubble that should be expanded and expands if needed. - * - * @param key the {@link NotificationEntry#key} associated with the bubble to expand. - * @deprecated replaced by setSelectedBubble(Bubble) + setExpanded(true) - */ - @Deprecated - void setExpandedBubble(String key) { - Bubble bubbleToExpand = mBubbleData.getBubbleWithKey(key); - if (bubbleToExpand != null) { - setSelectedBubble(bubbleToExpand); - bubbleToExpand.setShowInShade(false); - setExpanded(true); - } - } - // via BubbleData.Listener void addBubble(Bubble bubble) { if (DEBUG_BUBBLE_STACK_VIEW) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java index 563a0a7e43e1..31656a00c3ed 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java @@ -961,6 +961,13 @@ public class PhysicsAnimationLayout extends FrameLayout { if (view != null) { final SpringAnimation animation = (SpringAnimation) view.getTag(getTagIdForProperty(property)); + + // If the animation is null, the view was probably removed from the layout before + // the animation started. + if (animation == null) { + return; + } + if (afterCallbacks != null) { animation.addEndListener(new OneTimeEndListener() { @Override diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt new file mode 100644 index 000000000000..e6cdf50580d8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls + +import android.service.controls.Control + +data class ControlStatus(val control: Control, val favorite: Boolean, val removed: Boolean = false)
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt new file mode 100644 index 000000000000..265ddd8043b6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls + +import android.content.Context +import android.content.pm.ServiceInfo +import com.android.settingslib.applications.DefaultAppInfo + +class ControlsServiceInfo( + context: Context, + serviceInfo: ServiceInfo +) : DefaultAppInfo( + context, + context.packageManager, + context.userId, + serviceInfo.componentName +)
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt new file mode 100644 index 000000000000..b6cca3f5b4e1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.controller + +import android.content.ComponentName +import android.service.controls.DeviceTypes +import android.util.Log + +/** + * Stores basic information about a [Control] to persist and keep track of favorites. + */ +data class ControlInfo( + val component: ComponentName, + val controlId: String, + val controlTitle: CharSequence, + @DeviceTypes.DeviceType val deviceType: Int +) { + + companion object { + private const val TAG = "ControlInfo" + private const val SEPARATOR = ":" + fun createFromString(string: String): ControlInfo? { + val parts = string.split(SEPARATOR) + val component = ComponentName.unflattenFromString(parts[0]) + if (parts.size != 4 || component == null) { + Log.e(TAG, "Cannot parse ControlInfo from $string") + return null + } + val type = try { + parts[3].toInt() + } catch (e: Exception) { + Log.e(TAG, "Cannot parse deviceType from ${parts[3]}") + return null + } + return ControlInfo( + component, + parts[1], + parts[2], + if (DeviceTypes.validDeviceType(type)) type else DeviceTypes.TYPE_UNKNOWN) + } + } + override fun toString(): String { + return component.flattenToString() + + "$SEPARATOR$controlId$SEPARATOR$controlTitle$SEPARATOR$deviceType" + } + + class Builder { + lateinit var componentName: ComponentName + lateinit var controlId: String + lateinit var controlTitle: CharSequence + var deviceType: Int = DeviceTypes.TYPE_UNKNOWN + + fun build() = ControlInfo(componentName, controlId, controlTitle, deviceType) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt new file mode 100644 index 000000000000..6b7fc4b7e827 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.controller + +import android.content.ComponentName +import android.service.controls.Control +import android.service.controls.actions.ControlAction + +interface ControlsBindingController { + fun bindAndLoad(component: ComponentName, callback: (List<Control>) -> Unit) + fun bindServices(components: List<ComponentName>) + fun subscribe(controls: List<ControlInfo>) + fun action(controlInfo: ControlInfo, action: ControlAction) + fun unsubscribe() +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt new file mode 100644 index 000000000000..80e48b925fc9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.controller + +import android.content.ComponentName +import android.content.Context +import android.os.IBinder +import android.service.controls.Control +import android.service.controls.IControlsProviderCallback +import android.service.controls.actions.ControlAction +import android.util.ArrayMap +import android.util.Log +import com.android.internal.annotations.GuardedBy +import com.android.internal.annotations.VisibleForTesting +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.util.concurrency.DelayableExecutor +import dagger.Lazy +import java.util.concurrent.atomic.AtomicBoolean +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +@VisibleForTesting +open class ControlsBindingControllerImpl @Inject constructor( + private val context: Context, + @Background private val backgroundExecutor: DelayableExecutor, + private val lazyController: Lazy<ControlsController> +) : ControlsBindingController { + + companion object { + private const val TAG = "ControlsBindingControllerImpl" + } + + private val refreshing = AtomicBoolean(false) + + @GuardedBy("componentMap") + private val tokenMap: MutableMap<IBinder, ControlsProviderLifecycleManager> = + ArrayMap<IBinder, ControlsProviderLifecycleManager>() + @GuardedBy("componentMap") + private val componentMap: MutableMap<ComponentName, ControlsProviderLifecycleManager> = + ArrayMap<ComponentName, ControlsProviderLifecycleManager>() + + private val serviceCallback = object : IControlsProviderCallback.Stub() { + override fun onLoad(token: IBinder, controls: MutableList<Control>) { + backgroundExecutor.execute(OnLoadRunnable(token, controls)) + } + + override fun onRefreshState(token: IBinder, controlStates: List<Control>) { + if (!refreshing.get()) { + Log.d(TAG, "Refresh outside of window for token:$token") + } else { + backgroundExecutor.execute(OnRefreshStateRunnable(token, controlStates)) + } + } + + override fun onControlActionResponse( + token: IBinder, + controlId: String, + @ControlAction.ResponseResult response: Int + ) { + backgroundExecutor.execute(OnActionResponseRunnable(token, controlId, response)) + } + } + + @VisibleForTesting + internal open fun createProviderManager(component: ComponentName): + ControlsProviderLifecycleManager { + return ControlsProviderLifecycleManager( + context, + backgroundExecutor, + serviceCallback, + component + ) + } + + private fun retrieveLifecycleManager(component: ComponentName): + ControlsProviderLifecycleManager { + synchronized(componentMap) { + val provider = componentMap.getOrPut(component) { + createProviderManager(component) + } + tokenMap.putIfAbsent(provider.token, provider) + return provider + } + } + + override fun bindAndLoad(component: ComponentName, callback: (List<Control>) -> Unit) { + val provider = retrieveLifecycleManager(component) + provider.maybeBindAndLoad(callback) + } + + override fun subscribe(controls: List<ControlInfo>) { + val controlsByComponentName = controls.groupBy { it.component } + if (refreshing.compareAndSet(false, true)) { + controlsByComponentName.forEach { + val provider = retrieveLifecycleManager(it.key) + backgroundExecutor.execute { + provider.maybeBindAndSubscribe(it.value.map { it.controlId }) + } + } + } + // Unbind unneeded providers + val providersWithFavorites = controlsByComponentName.keys + synchronized(componentMap) { + componentMap.forEach { + if (it.key !in providersWithFavorites) { + backgroundExecutor.execute { it.value.unbindService() } + } + } + } + } + + override fun unsubscribe() { + if (refreshing.compareAndSet(true, false)) { + val providers = synchronized(componentMap) { + componentMap.values.toList() + } + providers.forEach { + backgroundExecutor.execute { it.unsubscribe() } + } + } + } + + override fun action(controlInfo: ControlInfo, action: ControlAction) { + val provider = retrieveLifecycleManager(controlInfo.component) + provider.maybeBindAndSendAction(controlInfo.controlId, action) + } + + override fun bindServices(components: List<ComponentName>) { + components.forEach { + val provider = retrieveLifecycleManager(it) + backgroundExecutor.execute { provider.bindPermanently() } + } + } + + private abstract inner class CallbackRunnable(val token: IBinder) : Runnable { + protected val provider: ControlsProviderLifecycleManager? = + synchronized(componentMap) { + tokenMap.get(token) + } + } + + private inner class OnLoadRunnable( + token: IBinder, + val list: List<Control> + ) : CallbackRunnable(token) { + override fun run() { + if (provider == null) { + Log.e(TAG, "No provider found for token:$token") + return + } + synchronized(componentMap) { + if (token !in tokenMap.keys) { + Log.e(TAG, "Provider for token:$token does not exist anymore") + return + } + } + provider.lastLoadCallback?.invoke(list) ?: run { + Log.w(TAG, "Null callback") + } + provider.maybeUnbindAndRemoveCallback() + } + } + + private inner class OnRefreshStateRunnable( + token: IBinder, + val list: List<Control> + ) : CallbackRunnable(token) { + override fun run() { + if (!refreshing.get()) { + Log.d(TAG, "onRefresh outside of window from:${provider?.componentName}") + } + provider?.let { + lazyController.get().refreshStatus(it.componentName, list) + } + } + } + + private inner class OnActionResponseRunnable( + token: IBinder, + val controlId: String, + @ControlAction.ResponseResult val response: Int + ) : CallbackRunnable(token) { + override fun run() { + provider?.let { + lazyController.get().onActionResponse(it.componentName, controlId, response) + } + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt new file mode 100644 index 000000000000..4d958224e917 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.controller + +import android.content.ComponentName +import android.service.controls.Control +import android.service.controls.actions.ControlAction +import com.android.systemui.controls.ControlStatus + +interface ControlsController { + val available: Boolean + + fun getFavoriteControls(): List<ControlInfo> + fun loadForComponent(componentName: ComponentName, callback: (List<ControlStatus>) -> Unit) + fun subscribeToFavorites() + fun changeFavoriteStatus(controlInfo: ControlInfo, state: Boolean) + fun unsubscribe() + fun action(controlInfo: ControlInfo, action: ControlAction) + fun refreshStatus(componentName: ComponentName, controls: List<Control>) + fun onActionResponse( + componentName: ComponentName, + controlId: String, + @ControlAction.ResponseResult response: Int + ) + fun clearFavorites() +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt new file mode 100644 index 000000000000..7e328e467129 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.controller + +import android.app.PendingIntent +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.os.Environment +import android.provider.Settings +import android.service.controls.Control +import android.service.controls.actions.ControlAction +import android.util.ArrayMap +import android.util.Log +import com.android.internal.annotations.GuardedBy +import com.android.systemui.DumpController +import com.android.systemui.Dumpable +import com.android.systemui.controls.ControlStatus +import com.android.systemui.controls.management.ControlsFavoritingActivity +import com.android.systemui.controls.ui.ControlsUiController +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.util.concurrency.DelayableExecutor +import java.io.FileDescriptor +import java.io.PrintWriter +import java.util.Optional +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ControlsControllerImpl @Inject constructor ( + private val context: Context, + @Background private val executor: DelayableExecutor, + private val uiController: ControlsUiController, + private val bindingController: ControlsBindingController, + private val optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>, + dumpController: DumpController +) : Dumpable, ControlsController { + + companion object { + private const val TAG = "ControlsControllerImpl" + const val CONTROLS_AVAILABLE = "systemui.controls_available" + } + + override val available = Settings.Secure.getInt( + context.contentResolver, CONTROLS_AVAILABLE, 0) != 0 + val persistenceWrapper = optionalWrapper.orElseGet { + ControlsFavoritePersistenceWrapper( + Environment.buildPath( + context.filesDir, + ControlsFavoritePersistenceWrapper.FILE_NAME), + executor + ) + } + + // Map of map: ComponentName -> (String -> ControlInfo) + @GuardedBy("currentFavorites") + private val currentFavorites = ArrayMap<ComponentName, MutableMap<String, ControlInfo>>() + + init { + if (available) { + dumpController.registerDumpable(this) + loadFavorites() + } + } + + private fun loadFavorites() { + val infos = persistenceWrapper.readFavorites() + synchronized(currentFavorites) { + infos.forEach { + currentFavorites.getOrPut(it.component, { ArrayMap<String, ControlInfo>() }) + .put(it.controlId, it) + } + } + } + + override fun loadForComponent( + componentName: ComponentName, + callback: (List<ControlStatus>) -> Unit + ) { + if (!available) { + Log.d(TAG, "Controls not available") + return + } + bindingController.bindAndLoad(componentName) { + synchronized(currentFavorites) { + val favoritesForComponentKeys: Set<String> = + currentFavorites.get(componentName)?.keys ?: emptySet() + val changed = updateFavoritesLocked(componentName, it) + if (changed) { + persistenceWrapper.storeFavorites(favoritesAsListLocked()) + } + val removed = findRemovedLocked(favoritesForComponentKeys, it) + callback(removed.map { currentFavorites.getValue(componentName).getValue(it) } + .map(::createRemovedStatus) + + it.map { ControlStatus(it, it.controlId in favoritesForComponentKeys) }) + } + } + } + + private fun createRemovedStatus(controlInfo: ControlInfo): ControlStatus { + val intent = Intent(context, ControlsFavoritingActivity::class.java).apply { + putExtra(ControlsFavoritingActivity.EXTRA_COMPONENT, controlInfo.component) + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP + } + val pendingIntent = PendingIntent.getActivity(context, + controlInfo.component.hashCode(), + intent, + 0) + val control = Control.StatelessBuilder(controlInfo.controlId, pendingIntent) + .setTitle(controlInfo.controlTitle) + .setDeviceType(controlInfo.deviceType) + .build() + return ControlStatus(control, true, true) + } + + @GuardedBy("currentFavorites") + private fun findRemovedLocked(favoriteKeys: Set<String>, list: List<Control>): Set<String> { + val controlsKeys = list.map { it.controlId } + return favoriteKeys.minus(controlsKeys) + } + + @GuardedBy("currentFavorites") + private fun updateFavoritesLocked(componentName: ComponentName, list: List<Control>): Boolean { + val favorites = currentFavorites.get(componentName) ?: mutableMapOf() + val favoriteKeys = favorites.keys + if (favoriteKeys.isEmpty()) return false // early return + var changed = false + list.forEach { + if (it.controlId in favoriteKeys) { + val value = favorites.getValue(it.controlId) + if (value.controlTitle != it.title || value.deviceType != it.deviceType) { + favorites[it.controlId] = value.copy(controlTitle = it.title, + deviceType = it.deviceType) + changed = true + } + } + } + return changed + } + + @GuardedBy("currentFavorites") + private fun favoritesAsListLocked(): List<ControlInfo> { + return currentFavorites.flatMap { it.value.values } + } + + override fun subscribeToFavorites() { + if (!available) { + Log.d(TAG, "Controls not available") + return + } + // Make a copy of the favorites list + val favorites = synchronized(currentFavorites) { + currentFavorites.flatMap { it.value.values.toList() } + } + bindingController.subscribe(favorites) + } + + override fun unsubscribe() { + if (!available) { + Log.d(TAG, "Controls not available") + return + } + bindingController.unsubscribe() + } + + override fun changeFavoriteStatus(controlInfo: ControlInfo, state: Boolean) { + if (!available) { + Log.d(TAG, "Controls not available") + return + } + var changed = false + val listOfControls = synchronized(currentFavorites) { + if (state) { + if (controlInfo.component !in currentFavorites) { + currentFavorites.put(controlInfo.component, ArrayMap<String, ControlInfo>()) + changed = true + } + val controlsForComponent = currentFavorites.getValue(controlInfo.component) + if (controlInfo.controlId !in controlsForComponent) { + controlsForComponent.put(controlInfo.controlId, controlInfo) + changed = true + } else { + if (controlsForComponent.getValue(controlInfo.controlId) != controlInfo) { + controlsForComponent.put(controlInfo.controlId, controlInfo) + changed = true + } + } + } else { + changed = currentFavorites.get(controlInfo.component) + ?.remove(controlInfo.controlId) != null + } + favoritesAsListLocked() + } + if (changed) { + persistenceWrapper.storeFavorites(listOfControls) + } + } + + override fun refreshStatus(componentName: ComponentName, controls: List<Control>) { + if (!available) { + Log.d(TAG, "Controls not available") + return + } + executor.execute { + synchronized(currentFavorites) { + val changed = updateFavoritesLocked(componentName, controls) + if (changed) { + persistenceWrapper.storeFavorites(favoritesAsListLocked()) + } + } + } + uiController.onRefreshState(componentName, controls) + } + + override fun onActionResponse(componentName: ComponentName, controlId: String, response: Int) { + if (!available) { + Log.d(TAG, "Controls not available") + return + } + uiController.onActionResponse(componentName, controlId, response) + } + + override fun getFavoriteControls(): List<ControlInfo> { + if (!available) { + Log.d(TAG, "Controls not available") + return emptyList() + } + synchronized(currentFavorites) { + return favoritesAsListLocked() + } + } + + override fun action(controlInfo: ControlInfo, action: ControlAction) { + bindingController.action(controlInfo, action) + } + + override fun clearFavorites() { + val changed = synchronized(currentFavorites) { + currentFavorites.isNotEmpty().also { + currentFavorites.clear() + } + } + if (changed) { + persistenceWrapper.storeFavorites(emptyList()) + } + } + + override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { + pw.println("ControlsController state:") + pw.println(" Favorites:") + synchronized(currentFavorites) { + currentFavorites.forEach { + it.value.forEach { + pw.println(" ${it.value}") + } + } + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt new file mode 100644 index 000000000000..6f2d71fd0f59 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.controller + +import android.app.ActivityManager +import android.content.ComponentName +import android.util.AtomicFile +import android.util.Log +import android.util.Slog +import android.util.Xml +import com.android.systemui.util.concurrency.DelayableExecutor +import libcore.io.IoUtils +import org.xmlpull.v1.XmlPullParser +import org.xmlpull.v1.XmlPullParserException +import java.io.File +import java.io.FileInputStream +import java.io.FileNotFoundException +import java.io.IOException + +class ControlsFavoritePersistenceWrapper( + val file: File, + val executor: DelayableExecutor +) { + + companion object { + private const val TAG = "ControlsFavoritePersistenceWrapper" + const val FILE_NAME = "controls_favorites.xml" + private const val TAG_CONTROLS = "controls" + private const val TAG_CONTROL = "control" + private const val TAG_COMPONENT = "component" + private const val TAG_ID = "id" + private const val TAG_TITLE = "title" + private const val TAG_TYPE = "type" + } + + val currentUser: Int + get() = ActivityManager.getCurrentUser() + + fun storeFavorites(list: List<ControlInfo>) { + executor.execute { + val atomicFile = AtomicFile(file) + val writer = try { + atomicFile.startWrite() + } catch (e: IOException) { + Log.e(TAG, "Failed to start write file", e) + return@execute + } + try { + Xml.newSerializer().apply { + setOutput(writer, "utf-8") + setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true) + startDocument(null, true) + startTag(null, TAG_CONTROLS) + list.forEach { + startTag(null, TAG_CONTROL) + attribute(null, TAG_COMPONENT, it.component.flattenToString()) + attribute(null, TAG_ID, it.controlId) + attribute(null, TAG_TITLE, it.controlTitle.toString()) + attribute(null, TAG_TYPE, it.deviceType.toString()) + endTag(null, TAG_CONTROL) + } + endTag(null, TAG_CONTROLS) + endDocument() + atomicFile.finishWrite(writer) + } + } catch (t: Throwable) { + Log.e(TAG, "Failed to write file, reverting to previous version") + atomicFile.failWrite(writer) + } finally { + IoUtils.closeQuietly(writer) + } + } + } + + fun readFavorites(): List<ControlInfo> { + if (!file.exists()) { + Log.d(TAG, "No favorites, returning empty list") + return emptyList() + } + val reader = try { + FileInputStream(file) + } catch (fnfe: FileNotFoundException) { + Slog.i(TAG, "No file found") + return emptyList() + } + try { + val parser = Xml.newPullParser() + parser.setInput(reader, null) + return parseXml(parser) + } catch (e: XmlPullParserException) { + throw IllegalStateException("Failed parsing favorites file: $file", e) + } catch (e: IOException) { + throw IllegalStateException("Failed parsing favorites file: $file", e) + } finally { + IoUtils.closeQuietly(reader) + } + } + + private fun parseXml(parser: XmlPullParser): List<ControlInfo> { + var type: Int = 0 + val infos = mutableListOf<ControlInfo>() + while (parser.next().also { type = it } != XmlPullParser.END_DOCUMENT) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue + } + val tagName = parser.name + if (tagName == TAG_CONTROL) { + val component = ComponentName.unflattenFromString( + parser.getAttributeValue(null, TAG_COMPONENT)) + val id = parser.getAttributeValue(null, TAG_ID) + val title = parser.getAttributeValue(null, TAG_TITLE) + val type = parser.getAttributeValue(null, TAG_TYPE)?.toInt() + if (component != null && id != null && title != null && type != null) { + infos.add(ControlInfo(component, id, title, type)) + } + } + } + return infos + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt new file mode 100644 index 000000000000..79057ad25b20 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.controller + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection +import android.os.Binder +import android.os.Bundle +import android.os.IBinder +import android.os.RemoteException +import android.service.controls.Control +import android.service.controls.ControlsProviderService.CALLBACK_BINDER +import android.service.controls.ControlsProviderService.CALLBACK_BUNDLE +import android.service.controls.ControlsProviderService.CALLBACK_TOKEN +import android.service.controls.IControlsProvider +import android.service.controls.IControlsProviderCallback +import android.service.controls.actions.ControlAction +import android.util.ArraySet +import android.util.Log +import com.android.internal.annotations.GuardedBy +import com.android.systemui.util.concurrency.DelayableExecutor +import java.util.concurrent.TimeUnit + +typealias LoadCallback = (List<Control>) -> Unit +class ControlsProviderLifecycleManager( + private val context: Context, + private val executor: DelayableExecutor, + private val serviceCallback: IControlsProviderCallback.Stub, + val componentName: ComponentName +) : IBinder.DeathRecipient { + + var lastLoadCallback: LoadCallback? = null + private set + val token: IBinder = Binder() + private var unbindImmediate = false + private var requiresBound = false + private var isBound = false + @GuardedBy("queuedMessages") + private val queuedMessages: MutableSet<Message> = ArraySet() + private var wrapper: ControlsProviderServiceWrapper? = null + private var bindTryCount = 0 + private val TAG = javaClass.simpleName + private var onLoadCanceller: Runnable? = null + + companion object { + private const val MSG_LOAD = 0 + private const val MSG_SUBSCRIBE = 1 + private const val MSG_UNSUBSCRIBE = 2 + private const val MSG_ON_ACTION = 3 + private const val MSG_UNBIND = 4 + private const val BIND_RETRY_DELAY = 1000L // ms + private const val LOAD_TIMEOUT = 5000L // ms + private const val MAX_BIND_RETRIES = 5 + private const val DEBUG = true + private val BIND_FLAGS = Context.BIND_AUTO_CREATE or Context.BIND_FOREGROUND_SERVICE or + Context.BIND_WAIVE_PRIORITY + } + + private val intent = Intent().apply { + component = componentName + putExtra(CALLBACK_BUNDLE, Bundle().apply { + putBinder(CALLBACK_BINDER, serviceCallback) + putBinder(CALLBACK_TOKEN, token) + }) + } + + private fun bindService(bind: Boolean) { + requiresBound = bind + if (bind) { + if (bindTryCount == MAX_BIND_RETRIES) { + return + } + if (DEBUG) { + Log.d(TAG, "Binding service $intent") + } + bindTryCount++ + try { + isBound = context.bindService(intent, serviceConnection, BIND_FLAGS) + } catch (e: SecurityException) { + Log.e(TAG, "Failed to bind to service", e) + isBound = false + } + } else { + if (DEBUG) { + Log.d(TAG, "Unbinding service $intent") + } + bindTryCount = 0 + wrapper = null + if (isBound) { + context.unbindService(serviceConnection) + isBound = false + } + } + } + + fun bindPermanently() { + unbindImmediate = false + unqueueMessage(Message.Unbind) + bindService(true) + } + + private val serviceConnection = object : ServiceConnection { + override fun onServiceConnected(name: ComponentName, service: IBinder) { + if (DEBUG) Log.d(TAG, "onServiceConnected $name") + bindTryCount = 0 + wrapper = ControlsProviderServiceWrapper(IControlsProvider.Stub.asInterface(service)) + try { + service.linkToDeath(this@ControlsProviderLifecycleManager, 0) + } catch (_: RemoteException) {} + handlePendingMessages() + } + + override fun onServiceDisconnected(name: ComponentName?) { + if (DEBUG) Log.d(TAG, "onServiceDisconnected $name") + isBound = false + bindService(false) + } + } + + private fun handlePendingMessages() { + val queue = synchronized(queuedMessages) { + ArraySet(queuedMessages).also { + queuedMessages.clear() + } + } + if (Message.Unbind in queue) { + bindService(false) + return + } + if (Message.Load in queue) { + load() + } + queue.filter { it is Message.Subscribe }.flatMap { (it as Message.Subscribe).list }.run { + subscribe(this) + } + queue.filter { it is Message.Action }.forEach { + val msg = it as Message.Action + onAction(msg.id, msg.action) + } + } + + override fun binderDied() { + if (wrapper == null) return + wrapper = null + if (requiresBound) { + if (DEBUG) { + Log.d(TAG, "binderDied") + } + // Try rebinding some time later + } + } + + private fun queueMessage(message: Message) { + synchronized(queuedMessages) { + queuedMessages.add(message) + } + } + + private fun unqueueMessage(message: Message) { + synchronized(queuedMessages) { + queuedMessages.removeIf { it.type == message.type } + } + } + + private fun load() { + if (DEBUG) { + Log.d(TAG, "load $componentName") + } + if (!(wrapper?.load() ?: false)) { + queueMessage(Message.Load) + binderDied() + } + } + + fun maybeBindAndLoad(callback: LoadCallback) { + unqueueMessage(Message.Unbind) + lastLoadCallback = callback + onLoadCanceller = executor.executeDelayed({ + // Didn't receive a response in time, log and send back empty list + Log.d(TAG, "Timeout waiting onLoad for $componentName") + serviceCallback.onLoad(token, emptyList()) + }, LOAD_TIMEOUT, TimeUnit.MILLISECONDS) + if (isBound) { + load() + } else { + queueMessage(Message.Load) + unbindImmediate = true + bindService(true) + } + } + + fun maybeBindAndSubscribe(controlIds: List<String>) { + if (isBound) { + subscribe(controlIds) + } else { + queueMessage(Message.Subscribe(controlIds)) + bindService(true) + } + } + + private fun subscribe(controlIds: List<String>) { + if (DEBUG) { + Log.d(TAG, "subscribe $componentName - $controlIds") + } + if (!(wrapper?.subscribe(controlIds) ?: false)) { + queueMessage(Message.Subscribe(controlIds)) + binderDied() + } + } + + fun maybeBindAndSendAction(controlId: String, action: ControlAction) { + if (isBound) { + onAction(controlId, action) + } else { + queueMessage(Message.Action(controlId, action)) + bindService(true) + } + } + + private fun onAction(controlId: String, action: ControlAction) { + if (DEBUG) { + Log.d(TAG, "onAction $componentName - $controlId") + } + if (!(wrapper?.onAction(controlId, action) ?: false)) { + queueMessage(Message.Action(controlId, action)) + binderDied() + } + } + + fun unsubscribe() { + if (DEBUG) { + Log.d(TAG, "unsubscribe $componentName") + } + unqueueMessage(Message.Subscribe(emptyList())) // Removes all subscribe messages + wrapper?.unsubscribe() + } + + fun maybeUnbindAndRemoveCallback() { + lastLoadCallback = null + onLoadCanceller?.run() + onLoadCanceller = null + if (unbindImmediate) { + bindService(false) + } + } + + fun unbindService() { + unbindImmediate = true + maybeUnbindAndRemoveCallback() + } + + sealed class Message { + abstract val type: Int + object Load : Message() { + override val type = MSG_LOAD + } + object Unbind : Message() { + override val type = MSG_UNBIND + } + class Subscribe(val list: List<String>) : Message() { + override val type = MSG_SUBSCRIBE + } + class Action(val id: String, val action: ControlAction) : Message() { + override val type = MSG_ON_ACTION + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapper.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapper.kt new file mode 100644 index 000000000000..882a10d54431 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapper.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.controller + +import android.service.controls.actions.ControlAction +import android.service.controls.IControlsProvider +import android.util.Log + +class ControlsProviderServiceWrapper(val service: IControlsProvider) { + companion object { + private const val TAG = "ControlsProviderServiceWrapper" + } + + private fun callThroughService(block: () -> Unit): Boolean { + try { + block() + return true + } catch (ex: Exception) { + Log.d(TAG, "Caught exception from ControlsProviderService", ex) + return false + } + } + + fun load(): Boolean { + return callThroughService { + service.load() + } + } + + fun subscribe(controlIds: List<String>): Boolean { + return callThroughService { + service.subscribe(controlIds) + } + } + + fun unsubscribe(): Boolean { + return callThroughService { + service.unsubscribe() + } + } + + fun onAction(controlId: String, action: ControlAction): Boolean { + return callThroughService { + service.onAction(controlId, action) + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt new file mode 100644 index 000000000000..859311e8767c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.dagger + +import android.app.Activity +import com.android.systemui.controls.controller.ControlsBindingController +import com.android.systemui.controls.controller.ControlsBindingControllerImpl +import com.android.systemui.controls.controller.ControlsController +import com.android.systemui.controls.controller.ControlsControllerImpl +import com.android.systemui.controls.controller.ControlsFavoritePersistenceWrapper +import com.android.systemui.controls.management.ControlsFavoritingActivity +import com.android.systemui.controls.management.ControlsListingController +import com.android.systemui.controls.management.ControlsListingControllerImpl +import com.android.systemui.controls.management.ControlsProviderSelectorActivity +import com.android.systemui.controls.ui.ControlsUiController +import com.android.systemui.controls.ui.ControlsUiControllerImpl +import dagger.Binds +import dagger.BindsOptionalOf +import dagger.Module +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +abstract class ControlsModule { + + @Binds + abstract fun provideControlsListingController( + controller: ControlsListingControllerImpl + ): ControlsListingController + + @Binds + abstract fun provideControlsController(controller: ControlsControllerImpl): ControlsController + + @Binds + abstract fun provideControlsBindingController( + controller: ControlsBindingControllerImpl + ): ControlsBindingController + + @Binds + abstract fun provideUiController(controller: ControlsUiControllerImpl): ControlsUiController + + @BindsOptionalOf + abstract fun optionalPersistenceWrapper(): ControlsFavoritePersistenceWrapper + + @Binds + @IntoMap + @ClassKey(ControlsProviderSelectorActivity::class) + abstract fun provideControlsProviderActivity( + activity: ControlsProviderSelectorActivity + ): Activity + + @Binds + @IntoMap + @ClassKey(ControlsFavoritingActivity::class) + abstract fun provideControlsFavoritingActivity( + activity: ControlsFavoritingActivity + ): Activity +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt new file mode 100644 index 000000000000..d62bb4def3aa --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.management + +import android.content.ComponentName +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.lifecycle.Lifecycle +import androidx.recyclerview.widget.RecyclerView +import com.android.settingslib.widget.CandidateInfo +import com.android.systemui.R +import java.util.concurrent.Executor + +/** + * Adapter for binding [CandidateInfo] related to [ControlsProviderService]. + * + * This class handles subscribing and keeping track of the list of valid applications for + * displaying. + * + * @param uiExecutor an executor on the view thread of the containing [RecyclerView] + * @param lifecycle the lifecycle of the containing [LifecycleOwner] to control listening status + * @param controlsListingController the controller to keep track of valid applications + * @param layoutInflater an inflater for the views in the containing [RecyclerView] + * @param onAppSelected a callback to indicate that an app has been selected in the list. + */ +class AppAdapter( + uiExecutor: Executor, + lifecycle: Lifecycle, + controlsListingController: ControlsListingController, + private val layoutInflater: LayoutInflater, + private val onAppSelected: (ComponentName?) -> Unit = {} +) : RecyclerView.Adapter<AppAdapter.Holder>() { + + private var listOfServices = emptyList<CandidateInfo>() + + private val callback = object : ControlsListingController.ControlsListingCallback { + override fun onServicesUpdated(list: List<CandidateInfo>) { + uiExecutor.execute { + listOfServices = list + notifyDataSetChanged() + } + } + } + + init { + controlsListingController.observe(lifecycle, callback) + } + + override fun onCreateViewHolder(parent: ViewGroup, i: Int): Holder { + return Holder(layoutInflater.inflate(R.layout.app_item, parent, false)) + } + + override fun getItemCount() = listOfServices.size + + override fun onBindViewHolder(holder: Holder, index: Int) { + holder.bindData(listOfServices[index]) + holder.itemView.setOnClickListener { + onAppSelected(ComponentName.unflattenFromString(listOfServices[index].key)) + } + } + + /** + * Holder for binding views in the [RecyclerView]- + */ + class Holder(view: View) : RecyclerView.ViewHolder(view) { + private val icon: ImageView = itemView.requireViewById(com.android.internal.R.id.icon) + private val title: TextView = itemView.requireViewById(com.android.internal.R.id.title) + + /** + * Bind data to the view + * @param data Information about the [ControlsProviderService] to bind to the data + */ + fun bindData(data: CandidateInfo) { + icon.setImageDrawable(data.loadIcon()) + title.text = data.loadLabel() + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt new file mode 100644 index 000000000000..e6d3c26ea7b8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.management + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.CheckBox +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.android.systemui.R +import com.android.systemui.controls.ControlStatus +import com.android.systemui.controls.controller.ControlInfo + +/** + * Adapter for binding [Control] information to views. + * + * @param layoutInflater an inflater for the views in the containing [RecyclerView] + * @param favoriteCallback a callback to be called when the favorite status of a [Control] is + * changed. The callback will take a [ControlInfo.Builder] that's + * pre-populated with the [Control] information and the new favorite + * status. + */ +class ControlAdapter( + private val layoutInflater: LayoutInflater, + private val favoriteCallback: (ControlInfo.Builder, Boolean) -> Unit +) : RecyclerView.Adapter<ControlAdapter.Holder>() { + + var listOfControls = emptyList<ControlStatus>() + + override fun onCreateViewHolder(parent: ViewGroup, i: Int): Holder { + return Holder(layoutInflater.inflate(R.layout.control_item, parent, false)) + } + + override fun getItemCount() = listOfControls.size + + override fun onBindViewHolder(holder: Holder, index: Int) { + holder.bindData(listOfControls[index], favoriteCallback) + } + + /** + * Holder for binding views in the [RecyclerView]- + */ + class Holder(view: View) : RecyclerView.ViewHolder(view) { + private val title: TextView = itemView.requireViewById(R.id.title) + private val subtitle: TextView = itemView.requireViewById(R.id.subtitle) + private val favorite: CheckBox = itemView.requireViewById(R.id.favorite) + + /** + * Bind data to the view + * @param data information about the [Control] + * @param callback a callback to be called when the favorite status of the [Control] is + * changed. The callback will take a [ControlInfo.Builder] that's + * pre-populated with the [Control] information and the new favorite status. + */ + fun bindData(data: ControlStatus, callback: (ControlInfo.Builder, Boolean) -> Unit) { + title.text = data.control.title + subtitle.text = data.control.subtitle + favorite.isChecked = data.favorite + favorite.setOnClickListener { + val infoBuilder = ControlInfo.Builder().apply { + controlId = data.control.controlId + controlTitle = data.control.title + deviceType = data.control.deviceType + } + callback(infoBuilder, favorite.isChecked) + } + } + } + + fun setItems(list: List<ControlStatus>) { + listOfControls = list + notifyDataSetChanged() + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt new file mode 100644 index 000000000000..01c4fef67fd4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.management + +import android.app.Activity +import android.content.ComponentName +import android.os.Bundle +import android.view.LayoutInflater +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.android.systemui.controls.controller.ControlInfo +import com.android.systemui.controls.controller.ControlsControllerImpl +import com.android.systemui.dagger.qualifiers.Main +import java.util.concurrent.Executor +import javax.inject.Inject + +class ControlsFavoritingActivity @Inject constructor( + @Main private val executor: Executor, + private val controller: ControlsControllerImpl +) : Activity() { + + companion object { + private const val TAG = "ControlsFavoritingActivity" + const val EXTRA_APP = "extra_app_label" + const val EXTRA_COMPONENT = "extra_component" + } + + private lateinit var recyclerView: RecyclerView + private lateinit var adapter: ControlAdapter + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val app = intent.getCharSequenceExtra(EXTRA_APP) + val component = intent.getParcelableExtra<ComponentName>(EXTRA_COMPONENT) + + // If we have no component name, there's not much we can do. + val callback = component?.let { + { infoBuilder: ControlInfo.Builder, status: Boolean -> + infoBuilder.componentName = it + controller.changeFavoriteStatus(infoBuilder.build(), status) + } + } ?: { _, _ -> Unit } + + recyclerView = RecyclerView(applicationContext) + adapter = ControlAdapter(LayoutInflater.from(applicationContext), callback) + recyclerView.adapter = adapter + recyclerView.layoutManager = LinearLayoutManager(applicationContext) + + if (app != null) { + setTitle("Controls for $app") + } else { + setTitle("Controls") + } + setContentView(recyclerView) + + component?.let { + controller.loadForComponent(it) { + executor.execute { + adapter.setItems(it) + } + } + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt new file mode 100644 index 000000000000..09e0ce9fea8d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.management + +import android.content.ComponentName +import com.android.settingslib.widget.CandidateInfo +import com.android.systemui.statusbar.policy.CallbackController + +interface ControlsListingController : + CallbackController<ControlsListingController.ControlsListingCallback> { + + fun getCurrentServices(): List<CandidateInfo> + fun getAppLabel(name: ComponentName): CharSequence? = "" + + interface ControlsListingCallback { + fun onServicesUpdated(list: List<CandidateInfo>) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt new file mode 100644 index 000000000000..937216230123 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.management + +import android.content.ComponentName +import android.content.Context +import android.content.pm.ServiceInfo +import android.service.controls.ControlsProviderService +import android.util.Log +import com.android.internal.annotations.VisibleForTesting +import com.android.settingslib.applications.DefaultAppInfo +import com.android.settingslib.applications.ServiceListing +import com.android.settingslib.widget.CandidateInfo +import com.android.systemui.controls.ControlsServiceInfo +import com.android.systemui.dagger.qualifiers.Background +import java.util.concurrent.Executor +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Provides a listing of components to be used as ControlsServiceProvider. + * + * This controller keeps track of components that satisfy: + * + * * Has an intent-filter responding to [ControlsProviderService.CONTROLS_ACTION] + * * Has the bind permission `android.permission.BIND_CONTROLS` + */ +@Singleton +class ControlsListingControllerImpl @VisibleForTesting constructor( + private val context: Context, + @Background private val backgroundExecutor: Executor, + private val serviceListing: ServiceListing +) : ControlsListingController { + + @Inject + constructor(context: Context, executor: Executor): this( + context, + executor, + ServiceListing.Builder(context) + .setIntentAction(ControlsProviderService.CONTROLS_ACTION) + .setPermission("android.permission.BIND_CONTROLS") + .setNoun("Controls Provider") + .setSetting("controls_providers") + .setTag("controls_providers") + .build() + ) + + companion object { + private const val TAG = "ControlsListingControllerImpl" + } + + private var availableServices = emptyList<ServiceInfo>() + + init { + serviceListing.addCallback { + Log.d(TAG, "ServiceConfig reloaded") + availableServices = it.toList() + + backgroundExecutor.execute { + callbacks.forEach { + it.onServicesUpdated(getCurrentServices()) + } + } + } + } + + // All operations in background thread + private val callbacks = mutableSetOf<ControlsListingController.ControlsListingCallback>() + + /** + * Adds a callback to this controller. + * + * The callback will be notified after it is added as well as any time that the valid + * components change. + * + * @param listener a callback to be notified + */ + override fun addCallback(listener: ControlsListingController.ControlsListingCallback) { + backgroundExecutor.execute { + callbacks.add(listener) + if (callbacks.size == 1) { + serviceListing.setListening(true) + serviceListing.reload() + } else { + listener.onServicesUpdated(getCurrentServices()) + } + } + } + + /** + * Removes a callback from this controller. + * + * @param listener the callback to be removed. + */ + override fun removeCallback(listener: ControlsListingController.ControlsListingCallback) { + backgroundExecutor.execute { + callbacks.remove(listener) + if (callbacks.size == 0) { + serviceListing.setListening(false) + } + } + } + + /** + * @return a list of components that satisfy the requirements to be a + * [ControlsProviderService] + */ + override fun getCurrentServices(): List<CandidateInfo> = + availableServices.map { ControlsServiceInfo(context, it) } + + /** + * Get the localized label for the component. + * + * @param name the name of the component + * @return a label as returned by [CandidateInfo.loadLabel] or `null`. + */ + override fun getAppLabel(name: ComponentName): CharSequence? { + return getCurrentServices().firstOrNull { (it as? DefaultAppInfo)?.componentName == name } + ?.loadLabel() + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt new file mode 100644 index 000000000000..69af516b4ac9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.management + +import android.content.ComponentName +import android.content.Intent +import android.os.Bundle +import android.view.LayoutInflater +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.util.LifecycleActivity +import java.util.concurrent.Executor +import javax.inject.Inject + +/** + * Activity to select an application to favorite the [Control] provided by them. + */ +class ControlsProviderSelectorActivity @Inject constructor( + @Main private val executor: Executor, + private val listingController: ControlsListingController +) : LifecycleActivity() { + + companion object { + private const val TAG = "ControlsProviderSelectorActivity" + } + + private lateinit var recyclerView: RecyclerView + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + recyclerView = RecyclerView(applicationContext) + recyclerView.adapter = AppAdapter(executor, lifecycle, listingController, + LayoutInflater.from(this), ::launchFavoritingActivity) + recyclerView.layoutManager = LinearLayoutManager(applicationContext) + + setContentView(recyclerView) + } + + /** + * Launch the [ControlsFavoritingActivity] for the specified component. + * @param component a component name for a [ControlsProviderService] + */ + fun launchFavoritingActivity(component: ComponentName?) { + component?.let { + val intent = Intent(applicationContext, ControlsFavoritingActivity::class.java).apply { + putExtra(ControlsFavoritingActivity.EXTRA_APP, listingController.getAppLabel(it)) + putExtra(ControlsFavoritingActivity.EXTRA_COMPONENT, it) + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP + } + startActivity(intent) + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt new file mode 100644 index 000000000000..0270c2b6b1b3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.ui + +import android.content.ComponentName +import android.service.controls.Control +import android.service.controls.actions.ControlAction + +interface ControlsUiController { + fun onRefreshState(componentName: ComponentName, controls: List<Control>) + fun onActionResponse( + componentName: ComponentName, + controlId: String, + @ControlAction.ResponseResult response: Int + ) +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt new file mode 100644 index 000000000000..0ace1263b49b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.ui + +import android.content.ComponentName +import android.service.controls.Control +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ControlsUiControllerImpl @Inject constructor() : ControlsUiController { + + override fun onRefreshState(componentName: ComponentName, controls: List<Control>) { + TODO("not implemented") + } + + override fun onActionResponse(componentName: ComponentName, controlId: String, response: Int) { + TODO("not implemented") + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java index df793109ae18..91f032d86a94 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java @@ -19,7 +19,9 @@ package com.android.systemui.dagger; import android.app.Activity; import com.android.systemui.ForegroundServicesDialog; +import com.android.systemui.bubbles.BubbleOverflowActivity; import com.android.systemui.keyguard.WorkLockActivity; +import com.android.systemui.screenrecord.ScreenRecordDialog; import com.android.systemui.settings.BrightnessDialog; import com.android.systemui.tuner.TunerActivity; @@ -29,7 +31,7 @@ import dagger.multibindings.ClassKey; import dagger.multibindings.IntoMap; /** - * Services and Activities that are injectable should go here. + * Activities that are injectable should go here. */ @Module public abstract class DefaultActivityBinder { @@ -56,4 +58,16 @@ public abstract class DefaultActivityBinder { @IntoMap @ClassKey(BrightnessDialog.class) public abstract Activity bindBrightnessDialog(BrightnessDialog activity); + + /** Inject into ScreenRecordDialog */ + @Binds + @IntoMap + @ClassKey(ScreenRecordDialog.class) + public abstract Activity bindScreenRecordDialog(ScreenRecordDialog activity); + + /** Inject into BubbleOverflowActivity. */ + @Binds + @IntoMap + @ClassKey(BubbleOverflowActivity.class) + public abstract Activity bindBubbleOverflowActivity(BubbleOverflowActivity activity); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java index f790d9929846..f006acf1694d 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java @@ -22,6 +22,7 @@ import com.android.systemui.ImageWallpaper; import com.android.systemui.SystemUIService; import com.android.systemui.doze.DozeService; import com.android.systemui.keyguard.KeyguardService; +import com.android.systemui.screenrecord.RecordingService; import com.android.systemui.screenshot.TakeScreenshotService; import dagger.Binds; @@ -63,4 +64,10 @@ public abstract class DefaultServiceBinder { @IntoMap @ClassKey(TakeScreenshotService.class) public abstract Service bindTakeScreenshotService(TakeScreenshotService service); + + /** Inject into RecordingService */ + @Binds + @IntoMap + @ClassKey(RecordingService.class) + public abstract Service bindRecordingService(RecordingService service); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java index 20917bd9dcb0..2877ed045479 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java @@ -20,6 +20,7 @@ import com.android.systemui.ActivityStarterDelegate; import com.android.systemui.appops.AppOpsController; import com.android.systemui.appops.AppOpsControllerImpl; import com.android.systemui.classifier.FalsingManagerProxy; +import com.android.systemui.controls.dagger.ControlsModule; import com.android.systemui.doze.DozeHost; import com.android.systemui.globalactions.GlobalActionsComponent; import com.android.systemui.globalactions.GlobalActionsImpl; @@ -85,7 +86,7 @@ import dagger.Module; /** * Maps interfaces to implementations for use with Dagger. */ -@Module +@Module(includes = {ControlsModule.class}) public abstract class DependencyBinder { /** diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index b19523866814..a6fa4146300d 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -30,10 +30,8 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.Recents; import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl; -import com.android.systemui.statusbar.notification.collection.NotificationRowBinder; -import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl; -import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; import com.android.systemui.statusbar.notification.people.PeopleHubModule; import com.android.systemui.statusbar.phone.KeyguardLiftController; import com.android.systemui.statusbar.phone.StatusBar; @@ -113,9 +111,4 @@ public abstract class SystemUIModule { @Singleton @Binds abstract SystemClock bindSystemClock(SystemClockImpl systemClock); - - @Singleton - @Binds - abstract NotifListBuilder bindNotifListBuilder(NotifListBuilderImpl impl); - } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 91bb80c60e3f..c138462d06c4 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -59,7 +59,6 @@ import android.telecom.TelecomManager; import android.telephony.PhoneStateListener; import android.telephony.ServiceState; import android.telephony.TelephonyManager; -import android.text.TextUtils; import android.util.ArraySet; import android.util.FeatureFlagUtils; import android.util.Log; @@ -444,7 +443,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mKeyguardManager.isDeviceLocked()) : null; - ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, panelViewController); + ActionsDialog dialog = new ActionsDialog( + mContext, mAdapter, panelViewController, isControlsEnabled(mContext)); dialog.setCanceledOnTouchOutside(false); // Handled by the custom class. dialog.setKeyguardShowing(mKeyguardShowing); @@ -518,7 +518,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, @Override public boolean shouldBeSeparated() { - return shouldUseSeparatedView(); + return !isControlsEnabled(mContext); } @Override @@ -1154,6 +1154,9 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } protected int getActionLayoutId(Context context) { + if (isControlsEnabled(context)) { + return com.android.systemui.R.layout.global_actions_grid_item_v2; + } return com.android.systemui.R.layout.global_actions_grid_item; } @@ -1415,8 +1418,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } else if (TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) { // Airplane mode can be changed after ECM exits if airplane toggle button // is pressed during ECM mode - if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) && - mIsWaitingForEcmExit) { + if (!(intent.getBooleanExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false)) + && mIsWaitingForEcmExit) { mIsWaitingForEcmExit = false; changeAirplaneModeSystemSetting(true); } @@ -1526,15 +1529,18 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private ResetOrientationData mResetOrientationData; private boolean mHadTopUi; private final StatusBarWindowController mStatusBarWindowController; + private boolean mControlsEnabled; ActionsDialog(Context context, MyAdapter adapter, - GlobalActionsPanelPlugin.PanelViewController plugin) { + GlobalActionsPanelPlugin.PanelViewController plugin, + boolean controlsEnabled) { super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions); mContext = context; mAdapter = adapter; mColorExtractor = Dependency.get(SysuiColorExtractor.class); mStatusBarService = Dependency.get(IStatusBarService.class); mStatusBarWindowController = Dependency.get(StatusBarWindowController.class); + mControlsEnabled = controlsEnabled; // Window initialization Window window = getWindow(); @@ -1651,6 +1657,10 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } private int getGlobalActionsLayoutId(Context context) { + if (mControlsEnabled) { + return com.android.systemui.R.layout.global_actions_grid_v2; + } + int rotation = RotationUtils.getRotation(context); boolean useGridLayout = isForceGridEnabled(context) || (shouldUsePanel() && rotation == RotationUtils.ROTATION_NONE); @@ -1854,4 +1864,9 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private static boolean shouldUseSeparatedView() { return true; } + + private static boolean isControlsEnabled(Context context) { + return Settings.Secure.getInt( + context.getContentResolver(), "systemui.controls_available", 0) == 1; + } } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java new file mode 100644 index 000000000000..6749f1d663eb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.globalactions; + +import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE; +import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE; +import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * Single row implementation of the button layout created by the global actions dialog. + */ +public class GlobalActionsFlatLayout extends GlobalActionsLayout { + public GlobalActionsFlatLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + mBackgroundsSet = true; + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + // backgrounds set only once, the first time onMeasure is called after inflation + // if (getListView() != null && !mBackgroundsSet) { + // setBackgrounds(); + // mBackgroundsSet = true; + // } + } + + @VisibleForTesting + protected void setupListView() { + ListGridLayout listView = getListView(); + listView.setExpectedCount(Math.min(2, mAdapter.countListItems())); + listView.setReverseSublists(shouldReverseSublists()); + listView.setReverseItems(shouldReverseListItems()); + listView.setSwapRowsAndColumns(shouldSwapRowsAndColumns()); + } + + @Override + public void onUpdateList() { + setupListView(); + super.onUpdateList(); + updateSeparatedItemSize(); + } + + /** + * If the separated view contains only one item, expand the bounds of that item to take up the + * entire view, so that the whole thing is touch-able. + */ + @VisibleForTesting + protected void updateSeparatedItemSize() { + ViewGroup separated = getSeparatedView(); + if (separated.getChildCount() == 0) { + return; + } + View firstChild = separated.getChildAt(0); + ViewGroup.LayoutParams childParams = firstChild.getLayoutParams(); + + if (separated.getChildCount() == 1) { + childParams.width = ViewGroup.LayoutParams.MATCH_PARENT; + childParams.height = ViewGroup.LayoutParams.MATCH_PARENT; + } else { + childParams.width = ViewGroup.LayoutParams.WRAP_CONTENT; + childParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; + } + } + + @Override + protected ListGridLayout getListView() { + return (ListGridLayout) super.getListView(); + } + + @Override + protected void removeAllListViews() { + ListGridLayout list = getListView(); + if (list != null) { + list.removeAllItems(); + } + } + + @Override + protected void addToListView(View v, boolean reverse) { + ListGridLayout list = getListView(); + if (list != null) { + list.addItem(v); + } + } + + @Override + public void removeAllItems() { + ViewGroup separatedList = getSeparatedView(); + ListGridLayout list = getListView(); + if (separatedList != null) { + separatedList.removeAllViews(); + } + if (list != null) { + list.removeAllItems(); + } + } + + /** + * Determines whether the ListGridLayout should fill sublists in the reverse order. + * Used to account for sublist ordering changing between landscape and seascape views. + */ + @VisibleForTesting + protected boolean shouldReverseSublists() { + if (getCurrentRotation() == ROTATION_SEASCAPE) { + return true; + } + return false; + } + + /** + * Determines whether the ListGridLayout should fill rows first instead of columns. + * Used to account for vertical/horizontal changes due to landscape or seascape rotations. + */ + @VisibleForTesting + protected boolean shouldSwapRowsAndColumns() { + if (getCurrentRotation() == ROTATION_NONE) { + return false; + } + return true; + } + + @Override + protected boolean shouldReverseListItems() { + int rotation = getCurrentRotation(); + boolean reverse = false; // should we add items to parents in the reverse order? + if (rotation == ROTATION_NONE + || rotation == ROTATION_SEASCAPE) { + reverse = !reverse; // if we're in portrait or seascape, reverse items + } + if (getCurrentLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { + reverse = !reverse; // if we're in an RTL language, reverse items (again) + } + return reverse; + } + + @VisibleForTesting + protected float getAnimationDistance() { + int rows = getListView().getRowCount(); + float gridItemSize = getContext().getResources().getDimension( + com.android.systemui.R.dimen.global_actions_grid_item_height); + return rows * gridItemSize / 2; + } + + @Override + public float getAnimationOffsetX() { + switch (getCurrentRotation()) { + case ROTATION_LANDSCAPE: + return getAnimationDistance(); + case ROTATION_SEASCAPE: + return -getAnimationDistance(); + default: // Portrait + return 0; + } + } + + @Override + public float getAnimationOffsetY() { + if (getCurrentRotation() == ROTATION_NONE) { + return getAnimationDistance(); + } + return 0; + } +} 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 f06c849f5d3c..2b53727f237e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java @@ -42,6 +42,7 @@ import com.android.systemui.qs.tiles.LocationTile; import com.android.systemui.qs.tiles.NfcTile; import com.android.systemui.qs.tiles.NightDisplayTile; import com.android.systemui.qs.tiles.RotationLockTile; +import com.android.systemui.qs.tiles.ScreenRecordTile; import com.android.systemui.qs.tiles.UiModeNightTile; import com.android.systemui.qs.tiles.UserTile; import com.android.systemui.qs.tiles.WifiTile; @@ -77,6 +78,7 @@ public class QSFactoryImpl implements QSFactory { private final Provider<NfcTile> mNfcTileProvider; private final Provider<GarbageMonitor.MemoryTile> mMemoryTileProvider; private final Provider<UiModeNightTile> mUiModeNightTileProvider; + private final Provider<ScreenRecordTile> mScreenRecordTileProvider; private QSTileHost mHost; @@ -100,7 +102,8 @@ public class QSFactoryImpl implements QSFactory { Provider<NightDisplayTile> nightDisplayTileProvider, Provider<NfcTile> nfcTileProvider, Provider<GarbageMonitor.MemoryTile> memoryTileProvider, - Provider<UiModeNightTile> uiModeNightTileProvider) { + Provider<UiModeNightTile> uiModeNightTileProvider, + Provider<ScreenRecordTile> screenRecordTileProvider) { mWifiTileProvider = wifiTileProvider; mBluetoothTileProvider = bluetoothTileProvider; mControlsTileProvider = controlsTileProvider; @@ -121,6 +124,7 @@ public class QSFactoryImpl implements QSFactory { mNfcTileProvider = nfcTileProvider; mMemoryTileProvider = memoryTileProvider; mUiModeNightTileProvider = uiModeNightTileProvider; + mScreenRecordTileProvider = screenRecordTileProvider; } public void setHost(QSTileHost host) { @@ -179,6 +183,8 @@ public class QSFactoryImpl implements QSFactory { return mNfcTileProvider.get(); case "dark": return mUiModeNightTileProvider.get(); + case "screenrecord": + return mScreenRecordTileProvider.get(); } // Custom tiles diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java new file mode 100644 index 000000000000..596c3b9af8c2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles; + +import android.content.Intent; +import android.service.quicksettings.Tile; +import android.util.Log; + +import com.android.systemui.R; +import com.android.systemui.plugins.qs.QSTile; +import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.screenrecord.RecordingController; + +import javax.inject.Inject; + +/** + * Quick settings tile for screen recording + */ +public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> { + private static final String TAG = "ScreenRecordTile"; + private RecordingController mController; + private long mMillisUntilFinished = 0; + + @Inject + public ScreenRecordTile(QSHost host, RecordingController controller) { + super(host); + mController = controller; + } + + @Override + public BooleanState newTileState() { + return new BooleanState(); + } + + @Override + protected void handleClick() { + if (mController.isStarting()) { + cancelCountdown(); + } else if (mController.isRecording()) { + stopRecording(); + } else { + startCountdown(); + } + refreshState(); + } + + /** + * Refresh tile state + * @param millisUntilFinished Time until countdown completes, or 0 if not counting down + */ + public void refreshState(long millisUntilFinished) { + mMillisUntilFinished = millisUntilFinished; + refreshState(); + } + + @Override + protected void handleUpdateState(BooleanState state, Object arg) { + boolean isStarting = mController.isStarting(); + boolean isRecording = mController.isRecording(); + + state.label = mContext.getString(R.string.quick_settings_screen_record_label); + state.value = isRecording || isStarting; + state.state = (isRecording || isStarting) ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; + state.handlesLongClick = false; + + if (isRecording) { + state.icon = ResourceIcon.get(R.drawable.ic_qs_screenrecord); + state.secondaryLabel = mContext.getString(R.string.quick_settings_screen_record_stop); + } else if (isStarting) { + // round, since the timer isn't exact + int countdown = (int) Math.floorDiv(mMillisUntilFinished + 500, 1000); + // TODO update icon + state.icon = ResourceIcon.get(R.drawable.ic_qs_screenrecord); + state.secondaryLabel = String.format("%d...", countdown); + } else { + // TODO update icon + state.icon = ResourceIcon.get(R.drawable.ic_qs_screenrecord); + state.secondaryLabel = mContext.getString(R.string.quick_settings_screen_record_start); + } + } + + @Override + public int getMetricsCategory() { + return 0; + } + + @Override + public Intent getLongClickIntent() { + return null; + } + + @Override + protected void handleSetListening(boolean listening) { + } + + @Override + public CharSequence getTileLabel() { + return mContext.getString(R.string.quick_settings_screen_record_label); + } + + private void startCountdown() { + Log.d(TAG, "Starting countdown"); + // Close QS, otherwise the permission dialog appears beneath it + getHost().collapsePanels(); + mController.launchRecordPrompt(this); + } + + private void cancelCountdown() { + Log.d(TAG, "Cancelling countdown"); + mController.cancelCountdown(); + } + + private void stopRecording() { + Log.d(TAG, "Stopping recording from tile"); + mController.stopRecording(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java new file mode 100644 index 000000000000..188501e41044 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenrecord; + +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.CountDownTimer; +import android.util.Log; + +import com.android.systemui.qs.tiles.ScreenRecordTile; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Helper class to initiate a screen recording + */ +@Singleton +public class RecordingController { + private static final String TAG = "RecordingController"; + private static final String SYSUI_PACKAGE = "com.android.systemui"; + private static final String SYSUI_SCREENRECORD_LAUNCHER = + "com.android.systemui.screenrecord.ScreenRecordDialog"; + + private final Context mContext; + private boolean mIsStarting; + private boolean mIsRecording; + private ScreenRecordTile mTileToUpdate; + private PendingIntent mStopIntent; + private CountDownTimer mCountDownTimer = null; + + /** + * Create a new RecordingController + * @param context Context for the controller + */ + @Inject + public RecordingController(Context context) { + mContext = context; + } + + /** + * Show dialog of screen recording options to user. + */ + public void launchRecordPrompt(ScreenRecordTile tileToUpdate) { + final ComponentName launcherComponent = new ComponentName(SYSUI_PACKAGE, + SYSUI_SCREENRECORD_LAUNCHER); + final Intent intent = new Intent(); + intent.setComponent(launcherComponent); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra("com.android.systemui.screenrecord.EXTRA_SETTINGS_ONLY", true); + mContext.startActivity(intent); + + mTileToUpdate = tileToUpdate; + } + + /** + * Start counting down in preparation to start a recording + * @param ms Time in ms to count down + * @param startIntent Intent to start a recording + * @param stopIntent Intent to stop a recording + */ + public void startCountdown(long ms, PendingIntent startIntent, PendingIntent stopIntent) { + mIsStarting = true; + mStopIntent = stopIntent; + + mCountDownTimer = new CountDownTimer(ms, 1000) { + @Override + public void onTick(long millisUntilFinished) { + refreshTile(millisUntilFinished); + } + + @Override + public void onFinish() { + mIsStarting = false; + mIsRecording = true; + refreshTile(); + try { + startIntent.send(); + } catch (PendingIntent.CanceledException e) { + Log.e(TAG, "Pending intent was cancelled: " + e.getMessage()); + } + } + }; + + mCountDownTimer.start(); + } + + private void refreshTile() { + refreshTile(0); + } + + private void refreshTile(long millisUntilFinished) { + if (mTileToUpdate != null) { + mTileToUpdate.refreshState(millisUntilFinished); + } else { + Log.e(TAG, "No tile to refresh"); + } + } + + /** + * Cancel a countdown in progress. This will not stop the recording if it already started. + */ + public void cancelCountdown() { + if (mCountDownTimer != null) { + mCountDownTimer.cancel(); + } else { + Log.e(TAG, "Timer was null"); + } + mIsStarting = false; + refreshTile(); + } + + /** + * Check if the recording is currently counting down to begin + * @return + */ + public boolean isStarting() { + return mIsStarting; + } + + /** + * Check if the recording is ongoing + * @return + */ + public boolean isRecording() { + return mIsRecording; + } + + /** + * Stop the recording + */ + public void stopRecording() { + updateState(false); + try { + mStopIntent.send(); + } catch (PendingIntent.CanceledException e) { + Log.e(TAG, "Error stopping: " + e.getMessage()); + } + refreshTile(); + } + + /** + * Update the current status + * @param isRecording + */ + public void updateState(boolean isRecording) { + mIsRecording = isRecording; + refreshTile(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java index 77c3ad971ef0..1b321685e88b 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java @@ -53,10 +53,14 @@ import java.nio.file.Files; import java.text.SimpleDateFormat; import java.util.Date; +import javax.inject.Inject; + /** * A service which records the device screen and optionally microphone input. */ public class RecordingService extends Service { + public static final int REQUEST_CODE = 2; + private static final int NOTIFICATION_ID = 1; private static final String TAG = "RecordingService"; private static final String CHANNEL_ID = "screen_record"; @@ -65,7 +69,6 @@ public class RecordingService extends Service { private static final String EXTRA_PATH = "extra_path"; private static final String EXTRA_USE_AUDIO = "extra_useAudio"; private static final String EXTRA_SHOW_TAPS = "extra_showTaps"; - private static final int REQUEST_CODE = 2; private static final String ACTION_START = "com.android.systemui.screenrecord.START"; private static final String ACTION_STOP = "com.android.systemui.screenrecord.STOP"; @@ -81,6 +84,7 @@ public class RecordingService extends Service { private static final int AUDIO_BIT_RATE = 16; private static final int AUDIO_SAMPLE_RATE = 44100; + private final RecordingController mController; private MediaProjectionManager mMediaProjectionManager; private MediaProjection mMediaProjection; private Surface mInputSurface; @@ -92,6 +96,11 @@ public class RecordingService extends Service { private boolean mShowTaps; private File mTempFile; + @Inject + public RecordingService(RecordingController controller) { + mController = controller; + } + /** * Get an intent to start the recording service. * @@ -272,6 +281,7 @@ public class RecordingService extends Service { null); mMediaRecorder.start(); + mController.updateState(true); } catch (IOException e) { Log.e(TAG, "Error starting screen recording: " + e.getMessage()); e.printStackTrace(); @@ -285,7 +295,7 @@ public class RecordingService extends Service { NotificationChannel channel = new NotificationChannel( CHANNEL_ID, getString(R.string.screenrecord_name), - NotificationManager.IMPORTANCE_HIGH); + NotificationManager.IMPORTANCE_LOW); channel.setDescription(getString(R.string.screenrecord_channel_description)); channel.enableVibration(true); NotificationManager notificationManager = @@ -399,6 +409,7 @@ public class RecordingService extends Service { mInputSurface.release(); mVirtualDisplay.release(); stopSelf(); + mController.updateState(false); } private void saveRecording(NotificationManager notificationManager) { @@ -439,7 +450,12 @@ public class RecordingService extends Service { Settings.System.SHOW_TOUCHES, value); } - private static Intent getStopIntent(Context context) { + /** + * Get an intent to stop the recording service. + * @param context Context from the requesting activity + * @return + */ + public static Intent getStopIntent(Context context) { return new Intent(context, RecordingService.class).setAction(ACTION_STOP); } diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java index 27e9fbab3161..8324986123bd 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java @@ -18,55 +18,41 @@ package com.android.systemui.screenrecord; import android.Manifest; import android.app.Activity; +import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.media.projection.MediaProjectionManager; import android.os.Bundle; -import android.util.Log; -import android.widget.Button; -import android.widget.CheckBox; import android.widget.Toast; import com.android.systemui.R; +import javax.inject.Inject; + /** * Activity to select screen recording options */ public class ScreenRecordDialog extends Activity { - private static final String TAG = "ScreenRecord"; private static final int REQUEST_CODE_VIDEO_ONLY = 200; private static final int REQUEST_CODE_VIDEO_TAPS = 201; private static final int REQUEST_CODE_PERMISSIONS = 299; private static final int REQUEST_CODE_VIDEO_AUDIO = 300; private static final int REQUEST_CODE_VIDEO_AUDIO_TAPS = 301; private static final int REQUEST_CODE_PERMISSIONS_AUDIO = 399; - private boolean mUseAudio; - private boolean mShowTaps; + private static final long DELAY_MS = 3000; + + private final RecordingController mController; + + @Inject + public ScreenRecordDialog(RecordingController controller) { + mController = controller; + } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.screen_record_dialog); - - final CheckBox micCheckBox = findViewById(R.id.checkbox_mic); - final CheckBox tapsCheckBox = findViewById(R.id.checkbox_taps); - - final Button recordButton = findViewById(R.id.record_button); - recordButton.setOnClickListener(v -> { - mUseAudio = micCheckBox.isChecked(); - mShowTaps = tapsCheckBox.isChecked(); - Log.d(TAG, "Record button clicked: audio " + mUseAudio + ", taps " + mShowTaps); - - if (mUseAudio && checkSelfPermission(Manifest.permission.RECORD_AUDIO) - != PackageManager.PERMISSION_GRANTED) { - Log.d(TAG, "Requesting permission for audio"); - requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, - REQUEST_CODE_PERMISSIONS_AUDIO); - } else { - requestScreenCapture(); - } - }); + requestScreenCapture(); } private void requestScreenCapture() { @@ -74,18 +60,23 @@ public class ScreenRecordDialog extends Activity { Context.MEDIA_PROJECTION_SERVICE); Intent permissionIntent = mediaProjectionManager.createScreenCaptureIntent(); - if (mUseAudio) { + // TODO get saved settings + boolean useAudio = false; + boolean showTaps = false; + if (useAudio) { startActivityForResult(permissionIntent, - mShowTaps ? REQUEST_CODE_VIDEO_AUDIO_TAPS : REQUEST_CODE_VIDEO_AUDIO); + showTaps ? REQUEST_CODE_VIDEO_AUDIO_TAPS : REQUEST_CODE_VIDEO_AUDIO); } else { startActivityForResult(permissionIntent, - mShowTaps ? REQUEST_CODE_VIDEO_TAPS : REQUEST_CODE_VIDEO_ONLY); + showTaps ? REQUEST_CODE_VIDEO_TAPS : REQUEST_CODE_VIDEO_ONLY); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { - mShowTaps = (requestCode == REQUEST_CODE_VIDEO_TAPS + boolean showTaps = (requestCode == REQUEST_CODE_VIDEO_TAPS + || requestCode == REQUEST_CODE_VIDEO_AUDIO_TAPS); + boolean useAudio = (requestCode == REQUEST_CODE_VIDEO_AUDIO || requestCode == REQUEST_CODE_VIDEO_AUDIO_TAPS); switch (requestCode) { case REQUEST_CODE_VIDEO_TAPS: @@ -93,11 +84,17 @@ public class ScreenRecordDialog extends Activity { case REQUEST_CODE_VIDEO_ONLY: case REQUEST_CODE_VIDEO_AUDIO: if (resultCode == RESULT_OK) { - mUseAudio = (requestCode == REQUEST_CODE_VIDEO_AUDIO - || requestCode == REQUEST_CODE_VIDEO_AUDIO_TAPS); - startForegroundService( - RecordingService.getStartIntent(this, resultCode, data, mUseAudio, - mShowTaps)); + PendingIntent startIntent = PendingIntent.getForegroundService( + this, RecordingService.REQUEST_CODE, RecordingService.getStartIntent( + ScreenRecordDialog.this, resultCode, data, useAudio, + showTaps), + PendingIntent.FLAG_UPDATE_CURRENT + ); + PendingIntent stopIntent = PendingIntent.getService( + this, RecordingService.REQUEST_CODE, + RecordingService.getStopIntent(this), + PendingIntent.FLAG_UPDATE_CURRENT); + mController.startCountdown(DELAY_MS, startIntent, stopIntent); } else { Toast.makeText(this, getResources().getString(R.string.screenrecord_permission_error), diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 9f2bbc680897..27c95552c1d1 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -64,7 +64,6 @@ import android.view.WindowManager; import android.view.animation.Interpolator; import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.TextView; import android.widget.Toast; import com.android.systemui.R; @@ -529,9 +528,10 @@ public class GlobalScreenshot { mActionsView.removeAllViews(); for (Notification.Action action : actions) { - TextView actionChip = (TextView) inflater.inflate( + ScreenshotActionChip actionChip = (ScreenshotActionChip) inflater.inflate( R.layout.global_screenshot_action_chip, mActionsView, false); actionChip.setText(action.title); + actionChip.setIcon(action.getIcon(), true); actionChip.setOnClickListener(v -> { try { action.actionIntent.send(); @@ -545,11 +545,11 @@ public class GlobalScreenshot { } if (DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, SCREENSHOT_SCROLLING_ENABLED, false)) { - TextView scrollChip = (TextView) inflater.inflate( + ScreenshotActionChip scrollChip = (ScreenshotActionChip) inflater.inflate( R.layout.global_screenshot_action_chip, mActionsView, false); Toast scrollNotImplemented = Toast.makeText( mContext, "Not implemented", Toast.LENGTH_SHORT); - scrollChip.setText("Scroll"); // TODO (mkephart): add resource and translate + scrollChip.setText("Extend"); // TODO (mkephart): add resource and translate scrollChip.setOnClickListener(v -> scrollNotImplemented.show()); mActionsView.addView(scrollChip); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java new file mode 100644 index 000000000000..6edacd12a9d1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot; + +import android.annotation.ColorInt; +import android.content.Context; +import android.graphics.drawable.Icon; +import android.util.AttributeSet; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.systemui.R; + +/** + * View for a chip with an icon and text. + */ +public class ScreenshotActionChip extends LinearLayout { + + private ImageView mIcon; + private TextView mText; + private @ColorInt int mIconColor; + + public ScreenshotActionChip(Context context) { + this(context, null); + } + + public ScreenshotActionChip(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ScreenshotActionChip(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public ScreenshotActionChip(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + mIconColor = context.getColor(R.color.global_screenshot_button_text); + } + + @Override + protected void onFinishInflate() { + mIcon = findViewById(R.id.screenshot_action_chip_icon); + mText = findViewById(R.id.screenshot_action_chip_text); + } + + void setIcon(Icon icon, boolean tint) { + if (tint) { + icon.setTint(mIconColor); + } + mIcon.setImageIcon(icon); + } + + void setText(CharSequence text) { + mText.setText(text); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index 36dcaac15a5f..4a2283171694 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -43,7 +43,7 @@ import com.android.systemui.statusbar.NotificationRemoveInterceptor; import com.android.systemui.statusbar.NotificationUiAdjustment; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; -import com.android.systemui.statusbar.notification.collection.NotificationRowBinder; +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; import com.android.systemui.statusbar.notification.logging.NotifEvent; import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.statusbar.notification.logging.NotificationLogger; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java index e1268f6d60ef..eaa9d78c08f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java @@ -16,44 +16,135 @@ package com.android.systemui.statusbar.notification.collection; -import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; - +import java.util.Arrays; import java.util.List; - /** - * Utility class for dumping the results of a {@link NotifListBuilder} to a debug string. + * Utility class for dumping the results of a {@link ShadeListBuilder} to a debug string. */ public class ListDumper { - /** See class description */ - public static String dumpList(List<ListEntry> entries) { + /** + * Creates a debug string for a list of grouped notifications that will be printed + * in the order given in a tiered/tree structure. + * @param includeRecordKeeping whether to print out the Pluggables that caused the notification + * entry to be in its current state (ie: filter, lifeExtender) + */ + public static String dumpTree( + List<ListEntry> entries, + boolean includeRecordKeeping, + String indent) { StringBuilder sb = new StringBuilder(); - for (int i = 0; i < entries.size(); i++) { - ListEntry entry = entries.get(i); - dumpEntry(entry, Integer.toString(i), "", sb); + final String childEntryIndent = indent + INDENT; + for (int topEntryIndex = 0; topEntryIndex < entries.size(); topEntryIndex++) { + ListEntry entry = entries.get(topEntryIndex); + dumpEntry(entry, + Integer.toString(topEntryIndex), + indent, + sb, + true, + includeRecordKeeping); if (entry instanceof GroupEntry) { GroupEntry ge = (GroupEntry) entry; - for (int j = 0; j < ge.getChildren().size(); j++) { - dumpEntry( - ge.getChildren().get(j), - Integer.toString(j), - INDENT, - sb); + List<NotificationEntry> children = ge.getChildren(); + for (int childIndex = 0; childIndex < children.size(); childIndex++) { + dumpEntry(children.get(childIndex), + Integer.toString(topEntryIndex) + "." + Integer.toString(childIndex), + childEntryIndent, + sb, + true, + includeRecordKeeping); } } } return sb.toString(); } + /** + * Creates a debug string for a flat list of notifications + * @param includeRecordKeeping whether to print out the Pluggables that caused the notification + * entry to be in its current state (ie: filter, lifeExtender) + */ + public static String dumpList( + List<NotificationEntry> entries, + boolean includeRecordKeeping, + String indent) { + StringBuilder sb = new StringBuilder(); + for (int j = 0; j < entries.size(); j++) { + dumpEntry( + entries.get(j), + Integer.toString(j), + indent, + sb, + false, + includeRecordKeeping); + } + return sb.toString(); + } + private static void dumpEntry( - ListEntry entry, String index, String indent, StringBuilder sb) { + ListEntry entry, + String index, + String indent, + StringBuilder sb, + boolean includeParent, + boolean includeRecordKeeping) { sb.append(indent) .append("[").append(index).append("] ") - .append(entry.getKey()) - .append(" (parent=") - .append(entry.getParent() != null ? entry.getParent().getKey() : null) - .append(")\n"); + .append(entry.getKey()); + + if (includeParent) { + sb.append(" (parent=") + .append(entry.getParent() != null ? entry.getParent().getKey() : null) + .append(")"); + } + + if (entry.mNotifSection != null) { + sb.append(" sectionIndex=") + .append(entry.getSection()) + .append(" sectionName=") + .append(entry.mNotifSection.getName()); + } + + if (includeRecordKeeping) { + NotificationEntry notifEntry = entry.getRepresentativeEntry(); + StringBuilder rksb = new StringBuilder(); + + if (!notifEntry.mLifetimeExtenders.isEmpty()) { + String[] lifetimeExtenderNames = new String[notifEntry.mLifetimeExtenders.size()]; + for (int i = 0; i < lifetimeExtenderNames.length; i++) { + lifetimeExtenderNames[i] = notifEntry.mLifetimeExtenders.get(i).getName(); + } + rksb.append("lifetimeExtenders=") + .append(Arrays.toString(lifetimeExtenderNames)) + .append(" "); + } + + if (notifEntry.mExcludingFilter != null) { + rksb.append("filter=") + .append(notifEntry.mExcludingFilter) + .append(" "); + } + + if (notifEntry.mNotifPromoter != null) { + rksb.append("promoter=") + .append(notifEntry.mNotifPromoter) + .append(" "); + } + + if (notifEntry.hasInflationError()) { + rksb.append("hasInflationError "); + } + + String rkString = rksb.toString(); + if (!rkString.isEmpty()) { + sb.append("\n\t") + .append(indent) + .append(rkString); + } + } + + sb.append("\n"); } private static final String INDENT = " "; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java index dc68c4bdba78..56ad0e1df36f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java @@ -18,6 +18,8 @@ package com.android.systemui.statusbar.notification.collection; import android.annotation.Nullable; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection; + /** * Abstract superclass for top-level entries, i.e. things that can appear in the final notification * list shown to users. In practice, this means either GroupEntries or NotificationEntries. @@ -27,7 +29,9 @@ public abstract class ListEntry { @Nullable private GroupEntry mParent; @Nullable private GroupEntry mPreviousParent; - private int mSection; + @Nullable NotifSection mNotifSection; + + private int mSection = -1; int mFirstAddedIteration = -1; ListEntry(String key) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index 856b75b7e36c..c488c6bb8721 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -47,11 +47,19 @@ import android.util.ArrayMap; import android.util.Log; import com.android.internal.statusbar.IStatusBarService; -import com.android.systemui.statusbar.notification.collection.notifcollection.CoalescedEvent; -import com.android.systemui.statusbar.notification.collection.notifcollection.GroupCoalescer; -import com.android.systemui.statusbar.notification.collection.notifcollection.GroupCoalescer.BatchableNotificationHandler; +import com.android.systemui.DumpController; +import com.android.systemui.Dumpable; +import com.android.systemui.statusbar.notification.collection.coalescer.CoalescedEvent; +import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer; +import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer.BatchableNotificationHandler; +import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener; +import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; import com.android.systemui.util.Assert; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -88,7 +96,7 @@ import javax.inject.Singleton; */ @MainThread @Singleton -public class NotifCollection { +public class NotifCollection implements Dumpable { private final IStatusBarService mStatusBarService; private final Map<String, NotificationEntry> mNotificationSet = new ArrayMap<>(); @@ -103,9 +111,10 @@ public class NotifCollection { private boolean mAmDispatchingToOtherCode; @Inject - public NotifCollection(IStatusBarService statusBarService) { + public NotifCollection(IStatusBarService statusBarService, DumpController dumpController) { Assert.isMainThread(); mStatusBarService = statusBarService; + dumpController.registerDumpable(TAG, this); } /** Initializes the NotifCollection and registers it to receive notification events. */ @@ -123,36 +132,25 @@ public class NotifCollection { * Sets the class responsible for converting the collection into the list of currently-visible * notifications. */ - public void setBuildListener(CollectionReadyForBuildListener buildListener) { + void setBuildListener(CollectionReadyForBuildListener buildListener) { Assert.isMainThread(); mBuildListener = buildListener; } - /** - * Returns the list of "active" notifications, i.e. the notifications that are currently posted - * to the phone. In general, this tracks closely to the list maintained by NotificationManager, - * but it can diverge slightly due to lifetime extenders. - * - * The returned list is read-only, unsorted, unfiltered, and ungrouped. - */ - public Collection<NotificationEntry> getNotifs() { + /** @see NotifPipeline#getActiveNotifs() */ + Collection<NotificationEntry> getActiveNotifs() { Assert.isMainThread(); return mReadOnlyNotificationSet; } - /** - * Registers a listener to be informed when notifications are added, removed or updated. - */ - public void addCollectionListener(NotifCollectionListener listener) { + /** @see NotifPipeline#addCollectionListener(NotifCollectionListener) */ + void addCollectionListener(NotifCollectionListener listener) { Assert.isMainThread(); mNotifCollectionListeners.add(listener); } - /** - * Registers a lifetime extender. Lifetime extenders can cause notifications that have been - * dismissed or retracted to be temporarily retained in the collection. - */ - public void addNotificationLifetimeExtender(NotifLifetimeExtender extender) { + /** @see NotifPipeline#addNotificationLifetimeExtender(NotifLifetimeExtender) */ + void addNotificationLifetimeExtender(NotifLifetimeExtender extender) { Assert.isMainThread(); checkForReentrantCall(); if (mLifetimeExtenders.contains(extender)) { @@ -165,7 +163,7 @@ public class NotifCollection { /** * Dismiss a notification on behalf of the user. */ - public void dismissNotification( + void dismissNotification( NotificationEntry entry, @CancellationReason int reason, @NonNull DismissedByUserStats stats) { @@ -446,7 +444,22 @@ public class NotifCollection { REASON_TIMEOUT, }) @Retention(RetentionPolicy.SOURCE) - @interface CancellationReason {} + public @interface CancellationReason {} public static final int REASON_UNKNOWN = 0; + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + final List<NotificationEntry> entries = new ArrayList<>(getActiveNotifs()); + + pw.println("\t" + TAG + " unsorted/unfiltered notifications:"); + if (entries.size() == 0) { + pw.println("\t\t None"); + } + pw.println( + ListDumper.dumpList( + entries, + true, + "\t\t")); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java index 0d175574f16b..e7b772f1c7b2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java @@ -25,6 +25,9 @@ import android.service.notification.StatusBarNotification; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.statusbar.notification.InflationException; +import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater; +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; +import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationContentInflater; @@ -107,7 +110,7 @@ public class NotifInflaterImpl implements NotifInflater { DISMISS_SENTIMENT_NEUTRAL, NotificationVisibility.obtain(entry.getKey(), entry.getRanking().getRank(), - mNotifCollection.getNotifs().size(), + mNotifCollection.getActiveNotifs().size(), true, NotificationLogger.getNotificationLocation(entry)) )); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java new file mode 100644 index 000000000000..0377f57a7a9c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection; + +import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener; +import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener; +import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; + +import java.util.Collection; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * The system that constructs the "shade list", the filtered, grouped, and sorted list of + * notifications that are currently being displayed to the user in the notification shade. + * + * The pipeline proceeds through a series of stages in order to produce the final list (see below). + * Each stage exposes hooks and listeners to allow other code to participate. + * + * This list differs from the canonical one we receive from system server in a few ways: + * - Filtered: Some notifications are filtered out. For example, we filter out notifications whose + * views haven't been inflated yet. We also filter out some notifications if we're on the lock + * screen and notifications for other users. So participate, see + * {@link #addPreGroupFilter} and similar methods. + * - Grouped: Notifications that are part of the same group are clustered together into a single + * GroupEntry. These groups are then transformed in order to remove children or completely split + * them apart. To participate, see {@link #addPromoter}. + * - Sorted: All top-level notifications are sorted. To participate, see + * {@link #setSections} and {@link #setComparators} + * + * The exact order of all hooks is as follows: + * 0. Collection listeners are fired ({@link #addCollectionListener}). + * 1. Pre-group filters are fired on each notification ({@link #addPreGroupFilter}). + * 2. Initial grouping is performed (NotificationEntries will have their parents set + * appropriately). + * 3. OnBeforeTransformGroupListeners are fired ({@link #addOnBeforeTransformGroupsListener}) + * 4. NotifPromoters are called on each notification with a parent ({@link #addPromoter}) + * 5. OnBeforeSortListeners are fired ({@link #addOnBeforeSortListener}) + * 6. Top-level entries are assigned sections by NotifSections ({@link #setSections}) + * 7. Top-level entries within the same section are sorted by NotifComparators + * ({@link #setComparators}) + * 8. Pre-render filters are fired on each notification ({@link #addPreRenderFilter}) + * 9. OnBeforeRenderListListeners are fired ({@link #addOnBeforeRenderListListener}) + * 9. The list is handed off to the view layer to be rendered + */ +@Singleton +public class NotifPipeline { + private final NotifCollection mNotifCollection; + private final ShadeListBuilder mShadeListBuilder; + + @Inject + public NotifPipeline( + NotifCollection notifCollection, + ShadeListBuilder shadeListBuilder) { + mNotifCollection = notifCollection; + mShadeListBuilder = shadeListBuilder; + } + + /** + * Returns the list of "active" notifications, i.e. the notifications that are currently posted + * to the phone. In general, this tracks closely to the list maintained by NotificationManager, + * but it can diverge slightly due to lifetime extenders. + * + * The returned collection is read-only, unsorted, unfiltered, and ungrouped. + */ + public Collection<NotificationEntry> getActiveNotifs() { + return mNotifCollection.getActiveNotifs(); + } + + /** + * Registers a listener to be informed when notifications are added, removed or updated. + */ + public void addCollectionListener(NotifCollectionListener listener) { + mNotifCollection.addCollectionListener(listener); + } + + /** + * Registers a lifetime extender. Lifetime extenders can cause notifications that have been + * dismissed or retracted to be temporarily retained in the collection. + */ + public void addNotificationLifetimeExtender(NotifLifetimeExtender extender) { + mNotifCollection.addNotificationLifetimeExtender(extender); + } + + /** + * Registers a filter with the pipeline before grouping, promoting and sorting occurs. Filters + * are called on each notification in the order that they were registered. If any filter + * returns true, the notification is removed from the pipeline (and no other filters are + * called on that notif). + */ + public void addPreGroupFilter(NotifFilter filter) { + mShadeListBuilder.addPreGroupFilter(filter); + } + + /** + * Called after notifications have been filtered and after the initial grouping has been + * performed but before NotifPromoters have had a chance to promote children out of groups. + */ + public void addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener listener) { + mShadeListBuilder.addOnBeforeTransformGroupsListener(listener); + } + + /** + * Registers a promoter with the pipeline. Promoters are able to promote child notifications to + * top-level, i.e. move a notification that would be a child of a group and make it appear + * ungrouped. Promoters are called on each child notification in the order that they are + * registered. If any promoter returns true, the notification is removed from the group (and no + * other promoters are called on it). + */ + public void addPromoter(NotifPromoter promoter) { + mShadeListBuilder.addPromoter(promoter); + } + + /** + * Called after notifs have been filtered and groups have been determined but before sections + * have been determined or the notifs have been sorted. + */ + public void addOnBeforeSortListener(OnBeforeSortListener listener) { + mShadeListBuilder.addOnBeforeSortListener(listener); + } + + /** + * Sections that are used to sort top-level entries. If two entries have the same section, + * NotifComparators are consulted. Sections from this list are called in order for each + * notification passed through the pipeline. The first NotifSection to return true for + * {@link NotifSection#isInSection(ListEntry)} sets the entry as part of its Section. + */ + public void setSections(List<NotifSection> sections) { + mShadeListBuilder.setSections(sections); + } + + /** + * Comparators that are used to sort top-level entries that share the same section. The + * comparators are executed in order until one of them returns a non-zero result. If all return + * zero, the pipeline falls back to sorting by rank (and, failing that, Notification.when). + */ + public void setComparators(List<NotifComparator> comparators) { + mShadeListBuilder.setComparators(comparators); + } + + /** + * Registers a filter with the pipeline to filter right before rendering the list (after + * pre-group filtering, grouping, promoting and sorting occurs). Filters are + * called on each notification in the order that they were registered. If any filter returns + * true, the notification is removed from the pipeline (and no other filters are called on that + * notif). + */ + public void addPreRenderFilter(NotifFilter filter) { + mShadeListBuilder.addPreRenderFilter(filter); + } + + /** + * Called at the end of the pipeline after the notif list has been finalized but before it has + * been handed off to the view layer. + */ + public void addOnBeforeRenderListListener(OnBeforeRenderListListener listener) { + mShadeListBuilder.addOnBeforeRenderListListener(listener); + } + + /** + * Returns a read-only view in to the current shade list, i.e. the list of notifications that + * are currently present in the shade. If this method is called during pipeline execution it + * will return the current state of the list, which will likely be only partially-generated. + */ + public List<ListEntry> getShadeList() { + return mShadeListBuilder.getShadeList(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 7301fe1df398..2fcfb8c811aa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -59,6 +59,7 @@ import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.notification.InflationException; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGuts; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt index 3bbd722517f7..f7fe064724a9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.collection +import android.app.NotificationChannel import android.app.NotificationManager.IMPORTANCE_HIGH import android.app.NotificationManager.IMPORTANCE_MIN import android.service.notification.NotificationListenerService.Ranking @@ -176,12 +177,14 @@ open class NotificationRankingManager @Inject constructor( } entry.ranking = newRanking - val oldSbn = entry.sbn.cloneLight() val newOverrideGroupKey = newRanking.overrideGroupKey - if (!Objects.equals(oldSbn.overrideGroupKey, newOverrideGroupKey)) { + if (!Objects.equals(entry.sbn.overrideGroupKey, newOverrideGroupKey)) { + val oldGroupKey = entry.sbn.groupKey + val oldIsGroup = entry.sbn.isGroup + val oldIsGroupSummary = entry.sbn.notification.isGroupSummary entry.sbn.overrideGroupKey = newOverrideGroupKey - // TODO: notify group manager here? - groupManager.onEntryUpdated(entry, oldSbn) + groupManager.onEntryUpdated(entry, oldGroupKey, oldIsGroup, + oldIsGroupSummary) } } } @@ -189,9 +192,9 @@ open class NotificationRankingManager @Inject constructor( } private fun NotificationEntry.isPeopleNotification() = - sbn.isPeopleNotification() - private fun StatusBarNotification.isPeopleNotification() = - peopleNotificationIdentifier.isPeopleNotification(this) + sbn.isPeopleNotification(channel) + private fun StatusBarNotification.isPeopleNotification(channel: NotificationChannel) = + peopleNotificationIdentifier.isPeopleNotification(this, channel) private fun NotificationEntry.isHighPriority() = highPriorityProvider.isHighPriority(this) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index 19d90f083592..97f8ec5f5bb7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.notification.collection; import static com.android.systemui.statusbar.notification.collection.GroupEntry.ROOT_ENTRY; -import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpList; import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_BUILD_STARTED; import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_FINALIZING; import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_GROUPING; @@ -31,8 +30,10 @@ import static com.android.systemui.statusbar.notification.collection.listbuilder import android.annotation.MainThread; import android.annotation.Nullable; import android.util.ArrayMap; +import android.util.Pair; -import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; +import com.android.systemui.DumpController; +import com.android.systemui.Dumpable; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener; @@ -40,12 +41,15 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.Pipeli import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.SectionsProvider; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection; +import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener; import com.android.systemui.statusbar.notification.logging.NotifEvent; import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.util.Assert; import com.android.systemui.util.time.SystemClock; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -57,11 +61,13 @@ import javax.inject.Inject; import javax.inject.Singleton; /** - * The implementation of {@link NotifListBuilder}. + * The second half of {@link NotifPipeline}. Sits downstream of the NotifCollection and transforms + * its "notification set" into the "shade list", the filtered, grouped, and sorted list of + * notifications that are currently present in the notification shade. */ @MainThread @Singleton -public class NotifListBuilderImpl implements NotifListBuilder { +public class ShadeListBuilder implements Dumpable { private final SystemClock mSystemClock; private final NotifLog mNotifLog; @@ -77,7 +83,7 @@ public class NotifListBuilderImpl implements NotifListBuilder { private final List<NotifPromoter> mNotifPromoters = new ArrayList<>(); private final List<NotifFilter> mNotifPreRenderFilters = new ArrayList<>(); private final List<NotifComparator> mNotifComparators = new ArrayList<>(); - private SectionsProvider mSectionsProvider = new DefaultSectionsProvider(); + private final List<NotifSection> mNotifSections = new ArrayList<>(); private final List<OnBeforeTransformGroupsListener> mOnBeforeTransformGroupsListeners = new ArrayList<>(); @@ -90,10 +96,14 @@ public class NotifListBuilderImpl implements NotifListBuilder { private final List<ListEntry> mReadOnlyNotifList = Collections.unmodifiableList(mNotifList); @Inject - public NotifListBuilderImpl(SystemClock systemClock, NotifLog notifLog) { + public ShadeListBuilder( + SystemClock systemClock, + NotifLog notifLog, + DumpController dumpController) { Assert.isMainThread(); mSystemClock = systemClock; mNotifLog = notifLog; + dumpController.registerDumpable(TAG, this); } /** @@ -116,32 +126,28 @@ public class NotifListBuilderImpl implements NotifListBuilder { mOnRenderListListener = onRenderListListener; } - @Override - public void addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener listener) { + void addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener listener) { Assert.isMainThread(); mPipelineState.requireState(STATE_IDLE); mOnBeforeTransformGroupsListeners.add(listener); } - @Override - public void addOnBeforeSortListener(OnBeforeSortListener listener) { + void addOnBeforeSortListener(OnBeforeSortListener listener) { Assert.isMainThread(); mPipelineState.requireState(STATE_IDLE); mOnBeforeSortListeners.add(listener); } - @Override - public void addOnBeforeRenderListListener(OnBeforeRenderListListener listener) { + void addOnBeforeRenderListListener(OnBeforeRenderListListener listener) { Assert.isMainThread(); mPipelineState.requireState(STATE_IDLE); mOnBeforeRenderListListeners.add(listener); } - @Override - public void addPreGroupFilter(NotifFilter filter) { + void addPreGroupFilter(NotifFilter filter) { Assert.isMainThread(); mPipelineState.requireState(STATE_IDLE); @@ -149,8 +155,7 @@ public class NotifListBuilderImpl implements NotifListBuilder { filter.setInvalidationListener(this::onPreGroupFilterInvalidated); } - @Override - public void addPreRenderFilter(NotifFilter filter) { + void addPreRenderFilter(NotifFilter filter) { Assert.isMainThread(); mPipelineState.requireState(STATE_IDLE); @@ -158,8 +163,7 @@ public class NotifListBuilderImpl implements NotifListBuilder { filter.setInvalidationListener(this::onPreRenderFilterInvalidated); } - @Override - public void addPromoter(NotifPromoter promoter) { + void addPromoter(NotifPromoter promoter) { Assert.isMainThread(); mPipelineState.requireState(STATE_IDLE); @@ -167,17 +171,18 @@ public class NotifListBuilderImpl implements NotifListBuilder { promoter.setInvalidationListener(this::onPromoterInvalidated); } - @Override - public void setSectionsProvider(SectionsProvider provider) { + void setSections(List<NotifSection> sections) { Assert.isMainThread(); mPipelineState.requireState(STATE_IDLE); - mSectionsProvider = provider; - provider.setInvalidationListener(this::onSectionsProviderInvalidated); + mNotifSections.clear(); + for (NotifSection section : sections) { + mNotifSections.add(section); + section.setInvalidationListener(this::onNotifSectionInvalidated); + } } - @Override - public void setComparators(List<NotifComparator> comparators) { + void setComparators(List<NotifComparator> comparators) { Assert.isMainThread(); mPipelineState.requireState(STATE_IDLE); @@ -188,8 +193,7 @@ public class NotifListBuilderImpl implements NotifListBuilder { } } - @Override - public List<ListEntry> getActiveNotifs() { + List<ListEntry> getShadeList() { Assert.isMainThread(); return mReadOnlyNotifList; } @@ -230,12 +234,12 @@ public class NotifListBuilderImpl implements NotifListBuilder { rebuildListIfBefore(STATE_TRANSFORMING); } - private void onSectionsProviderInvalidated(SectionsProvider provider) { + private void onNotifSectionInvalidated(NotifSection section) { Assert.isMainThread(); - mNotifLog.log(NotifEvent.SECTIONS_PROVIDER_INVALIDATED, String.format( - "Sections provider \"%s\" invalidated; pipeline state is %d", - provider.getName(), + mNotifLog.log(NotifEvent.SECTION_INVALIDATED, String.format( + "Section \"%s\" invalidated; pipeline state is %d", + section.getName(), mPipelineState.getState())); rebuildListIfBefore(STATE_SORTING); @@ -275,7 +279,7 @@ public class NotifListBuilderImpl implements NotifListBuilder { } /** - * The core algorithm of the pipeline. See the top comment in {@link NotifListBuilder} for + * The core algorithm of the pipeline. See the top comment in {@link NotifPipeline} for * details on our contracts with other code. * * Once the build starts we are very careful to protect against reentrant code. Anything that @@ -318,7 +322,7 @@ public class NotifListBuilderImpl implements NotifListBuilder { sortList(); // Step 6: Filter out entries after pre-group filtering, grouping, promoting and sorting - // Now filters can see grouping information to determine whether to filter or not + // Now filters can see grouping information to determine whether to filter or not. mPipelineState.incrementTo(STATE_PRE_RENDER_FILTERING); filterNotifs(mNotifList, mNewNotifList, mNotifPreRenderFilters); applyNewNotifList(); @@ -331,7 +335,7 @@ public class NotifListBuilderImpl implements NotifListBuilder { // Step 6: Dispatch the new list, first to any listeners and then to the view layer mNotifLog.log(NotifEvent.DISPATCH_FINAL_LIST, "List finalized, is:\n" - + dumpList(mNotifList)); + + ListDumper.dumpTree(mNotifList, false, "\t\t")); dispatchOnBeforeRenderList(mReadOnlyNotifList); if (mOnRenderListListener != null) { mOnRenderListListener.onRenderList(mReadOnlyNotifList); @@ -580,6 +584,8 @@ public class NotifListBuilderImpl implements NotifListBuilder { * filtered out during any of the filtering steps. */ private void annulAddition(ListEntry entry) { + entry.setSection(-1); + entry.mNotifSection = null; entry.setParent(null); if (entry.mFirstAddedIteration == mIterationCount) { entry.mFirstAddedIteration = -1; @@ -589,11 +595,12 @@ public class NotifListBuilderImpl implements NotifListBuilder { private void sortList() { // Assign sections to top-level elements and sort their children for (ListEntry entry : mNotifList) { - entry.setSection(mSectionsProvider.getSection(entry)); + Pair<NotifSection, Integer> sectionWithIndex = applySections(entry); if (entry instanceof GroupEntry) { GroupEntry parent = (GroupEntry) entry; for (NotificationEntry child : parent.getChildren()) { - child.setSection(0); + child.mNotifSection = sectionWithIndex.first; + child.setSection(sectionWithIndex.second); } parent.sortChildren(sChildComparator); } @@ -754,6 +761,45 @@ public class NotifListBuilderImpl implements NotifListBuilder { return null; } + private Pair<NotifSection, Integer> applySections(ListEntry entry) { + final Pair<NotifSection, Integer> sectionWithIndex = findSection(entry); + final NotifSection section = sectionWithIndex.first; + final Integer sectionIndex = sectionWithIndex.second; + + if (section != entry.mNotifSection) { + if (entry.mNotifSection == null) { + mNotifLog.log(NotifEvent.SECTION_CHANGED, String.format( + "%s: sectioned by '%s' [index=%d].", + entry.getKey(), + section.getName(), + sectionIndex)); + } else { + mNotifLog.log(NotifEvent.SECTION_CHANGED, String.format( + "%s: section changed: '%s' [index=%d] -> '%s [index=%d]'.", + entry.getKey(), + entry.mNotifSection, + entry.getSection(), + section, + sectionIndex)); + } + + entry.mNotifSection = section; + entry.setSection(sectionIndex); + } + + return sectionWithIndex; + } + + private Pair<NotifSection, Integer> findSection(ListEntry entry) { + for (int i = 0; i < mNotifSections.size(); i++) { + NotifSection sectioner = mNotifSections.get(i); + if (sectioner.isInSection(entry)) { + return new Pair<>(sectioner, i); + } + } + return new Pair<>(sDefaultSection, mNotifSections.size()); + } + private void rebuildListIfBefore(@PipelineState.StateName int state) { mPipelineState.requireIsBefore(state); if (mPipelineState.is(STATE_IDLE)) { @@ -779,6 +825,19 @@ public class NotifListBuilderImpl implements NotifListBuilder { } } + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("\t" + TAG + " shade notifications:"); + if (getShadeList().size() == 0) { + pw.println("\t\t None"); + } + + pw.println(ListDumper.dumpTree( + getShadeList(), + true, + "\t\t")); + } + /** See {@link #setOnRenderListListener(OnRenderListListener)} */ public interface OnRenderListListener { /** @@ -790,16 +849,13 @@ public class NotifListBuilderImpl implements NotifListBuilder { void onRenderList(List<ListEntry> entries); } - private static class DefaultSectionsProvider extends SectionsProvider { - DefaultSectionsProvider() { - super("DefaultSectionsProvider"); - } - - @Override - public int getSection(ListEntry entry) { - return 0; - } - } + private static final NotifSection sDefaultSection = + new NotifSection("DefaultSection") { + @Override + public boolean isInSection(ListEntry entry) { + return true; + } + }; private static final String TAG = "NotifListBuilderImpl"; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CoalescedEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/CoalescedEvent.kt index b6218b4a9c47..143de8a27816 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CoalescedEvent.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/CoalescedEvent.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 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,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.collection.notifcollection +package com.android.systemui.statusbar.notification.collection.coalescer import android.service.notification.NotificationListenerService.Ranking import android.service.notification.StatusBarNotification diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/EventBatch.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/EventBatch.java index ac51178e26d7..2eec68b26347 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/EventBatch.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/EventBatch.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 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,7 +14,9 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.collection.notifcollection; +package com.android.systemui.statusbar.notification.collection.coalescer; + +import androidx.annotation.Nullable; import java.util.ArrayList; import java.util.List; @@ -35,6 +37,8 @@ public class EventBatch { */ final List<CoalescedEvent> mMembers = new ArrayList<>(); + @Nullable Runnable mCancelShortTimeout; + EventBatch(long createdTimestamp, String groupKey) { mCreatedTimestamp = createdTimestamp; this.mGroupKey = groupKey; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java index c3e3c5373b7b..f5890386a14f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 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,9 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.collection.notifcollection; +package com.android.systemui.statusbar.notification.collection.coalescer; +import static com.android.systemui.statusbar.notification.logging.NotifEvent.BATCH_MAX_TIMEOUT; import static com.android.systemui.statusbar.notification.logging.NotifEvent.COALESCED_EVENT; import static com.android.systemui.statusbar.notification.logging.NotifEvent.EARLY_BATCH_EMIT; import static com.android.systemui.statusbar.notification.logging.NotifEvent.EMIT_EVENT_BATCH; @@ -71,7 +72,8 @@ public class GroupCoalescer implements Dumpable { private final DelayableExecutor mMainExecutor; private final SystemClock mClock; private final NotifLog mLog; - private final long mGroupLingerDuration; + private final long mMinGroupLingerDuration; + private final long mMaxGroupLingerDuration; private BatchableNotificationHandler mHandler; @@ -82,22 +84,28 @@ public class GroupCoalescer implements Dumpable { public GroupCoalescer( @Main DelayableExecutor mainExecutor, SystemClock clock, NotifLog log) { - this(mainExecutor, clock, log, GROUP_LINGER_DURATION); + this(mainExecutor, clock, log, MIN_GROUP_LINGER_DURATION, MAX_GROUP_LINGER_DURATION); } /** - * @param groupLingerDuration How long, in ms, that notifications that are members of a group - * are delayed within the GroupCoalescer before being posted + * @param minGroupLingerDuration How long, in ms, to wait for another notification from the same + * group to arrive before emitting all pending events for that + * group. Each subsequent arrival of a group member resets the + * timer for that group. + * @param maxGroupLingerDuration The maximum time, in ms, that a group can linger in the + * coalescer before it's force-emitted. */ GroupCoalescer( @Main DelayableExecutor mainExecutor, SystemClock clock, NotifLog log, - long groupLingerDuration) { + long minGroupLingerDuration, + long maxGroupLingerDuration) { mMainExecutor = mainExecutor; mClock = clock; mLog = log; - mGroupLingerDuration = groupLingerDuration; + mMinGroupLingerDuration = minGroupLingerDuration; + mMaxGroupLingerDuration = maxGroupLingerDuration; } /** @@ -115,7 +123,7 @@ public class GroupCoalescer implements Dumpable { private final NotificationHandler mListener = new NotificationHandler() { @Override public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) { - maybeEmitBatch(sbn.getKey()); + maybeEmitBatch(sbn); applyRanking(rankingMap); final boolean shouldCoalesce = handleNotificationPosted(sbn, rankingMap); @@ -130,7 +138,7 @@ public class GroupCoalescer implements Dumpable { @Override public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) { - maybeEmitBatch(sbn.getKey()); + maybeEmitBatch(sbn); applyRanking(rankingMap); mHandler.onNotificationRemoved(sbn, rankingMap); } @@ -140,7 +148,7 @@ public class GroupCoalescer implements Dumpable { StatusBarNotification sbn, RankingMap rankingMap, int reason) { - maybeEmitBatch(sbn.getKey()); + maybeEmitBatch(sbn); applyRanking(rankingMap); mHandler.onNotificationRemoved(sbn, rankingMap, reason); } @@ -152,13 +160,20 @@ public class GroupCoalescer implements Dumpable { } }; - private void maybeEmitBatch(String memberKey) { - CoalescedEvent event = mCoalescedEvents.get(memberKey); + private void maybeEmitBatch(StatusBarNotification sbn) { + final CoalescedEvent event = mCoalescedEvents.get(sbn.getKey()); + final EventBatch batch = mBatches.get(sbn.getGroupKey()); if (event != null) { mLog.log(EARLY_BATCH_EMIT, String.format("Modification of %s triggered early emit of batched group %s", - memberKey, requireNonNull(event.getBatch()).mGroupKey)); + sbn.getKey(), requireNonNull(event.getBatch()).mGroupKey)); emitBatch(requireNonNull(event.getBatch())); + } else if (batch != null + && mClock.uptimeMillis() - batch.mCreatedTimestamp >= mMaxGroupLingerDuration) { + mLog.log(BATCH_MAX_TIMEOUT, + String.format("Modification of %s triggered timeout emit of batched group %s", + sbn.getKey(), batch.mGroupKey)); + emitBatch(batch); } } @@ -175,7 +190,8 @@ public class GroupCoalescer implements Dumpable { } if (sbn.isGroup()) { - EventBatch batch = startBatchingGroup(sbn.getGroupKey()); + final EventBatch batch = getOrBuildBatch(sbn.getGroupKey()); + CoalescedEvent event = new CoalescedEvent( sbn.getKey(), @@ -183,10 +199,10 @@ public class GroupCoalescer implements Dumpable { sbn, requireRanking(rankingMap, sbn.getKey()), batch); + mCoalescedEvents.put(event.getKey(), event); batch.mMembers.add(event); - - mCoalescedEvents.put(event.getKey(), event); + resetShortTimeout(batch); return true; } else { @@ -194,27 +210,39 @@ public class GroupCoalescer implements Dumpable { } } - private EventBatch startBatchingGroup(final String groupKey) { + private EventBatch getOrBuildBatch(final String groupKey) { EventBatch batch = mBatches.get(groupKey); if (batch == null) { - final EventBatch newBatch = new EventBatch(mClock.uptimeMillis(), groupKey); - mBatches.put(groupKey, newBatch); - mMainExecutor.executeDelayed(() -> emitBatch(newBatch), mGroupLingerDuration); - - batch = newBatch; + batch = new EventBatch(mClock.uptimeMillis(), groupKey); + mBatches.put(groupKey, batch); } return batch; } + private void resetShortTimeout(EventBatch batch) { + if (batch.mCancelShortTimeout != null) { + batch.mCancelShortTimeout.run(); + } + batch.mCancelShortTimeout = + mMainExecutor.executeDelayed( + () -> { + batch.mCancelShortTimeout = null; + emitBatch(batch); + }, + mMinGroupLingerDuration); + } + private void emitBatch(EventBatch batch) { if (batch != mBatches.get(batch.mGroupKey)) { - // If we emit a batch early, we don't want to emit it a second time when its timeout - // expires. - return; + throw new IllegalStateException("Cannot emit out-of-date batch " + batch.mGroupKey); } if (batch.mMembers.isEmpty()) { throw new IllegalStateException("Batch " + batch.mGroupKey + " cannot be empty"); } + if (batch.mCancelShortTimeout != null) { + batch.mCancelShortTimeout.run(); + batch.mCancelShortTimeout = null; + } mBatches.remove(batch.mGroupKey); @@ -299,5 +327,6 @@ public class GroupCoalescer implements Dumpable { void onNotificationBatchPosted(List<CoalescedEvent> events); } - private static final int GROUP_LINGER_DURATION = 500; + private static final int MIN_GROUP_LINGER_DURATION = 50; + private static final int MAX_GROUP_LINGER_DURATION = 500; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java index 898918eb076d..d8b2e4089f30 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java @@ -16,27 +16,21 @@ package com.android.systemui.statusbar.notification.collection.coordinator; -import com.android.systemui.statusbar.notification.collection.NotifCollection; -import com.android.systemui.statusbar.notification.collection.NotifCollectionListener; -import com.android.systemui.statusbar.notification.collection.NotifLifetimeExtender; -import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline; -import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable; /** - * Interface for registering callbacks to the {@link NewNotifPipeline}. - * - * This includes registering: - * {@link Pluggable}s to the {@link NotifListBuilder} - * {@link NotifCollectionListener}s and {@link NotifLifetimeExtender}s to {@link NotifCollection} + * Interface for registering callbacks to the {@link NotifPipeline}. */ public interface Coordinator { - /** * Called after the NewNotifPipeline is initialized. - * Coordinators should register their {@link Pluggable}s to the notifListBuilder - * and their {@link NotifCollectionListener}s and {@link NotifLifetimeExtender}s - * to the notifCollection in this method. + * Coordinators should register their listeners and {@link Pluggable}s to the pipeline. */ - void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder); + void attach(NotifPipeline pipeline); + + default NotifSection getSection() { + return null; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java index 5e7dd98fa9da..625d1b9686e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java @@ -23,9 +23,8 @@ import android.content.pm.PackageManager; import android.os.RemoteException; import android.service.notification.StatusBarNotification; -import com.android.systemui.statusbar.notification.collection.NotifCollection; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -52,10 +51,10 @@ public class DeviceProvisionedCoordinator implements Coordinator { } @Override - public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) { + public void attach(NotifPipeline pipeline) { mDeviceProvisionedController.addCallback(mDeviceProvisionedListener); - notifListBuilder.addPreGroupFilter(mNotifFilter); + pipeline.addPreGroupFilter(mNotifFilter); } private final NotifFilter mNotifFilter = new NotifFilter(TAG) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java index 62342b13f9cf..da119c1502c6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java @@ -25,12 +25,11 @@ import android.util.ArraySet; import com.android.systemui.ForegroundServiceController; import com.android.systemui.appops.AppOpsController; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.statusbar.notification.collection.NotifCollection; -import com.android.systemui.statusbar.notification.collection.NotifCollectionListener; -import com.android.systemui.statusbar.notification.collection.NotifLifetimeExtender; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; import java.util.HashMap; import java.util.Map; @@ -57,7 +56,7 @@ public class ForegroundCoordinator implements Coordinator { private final AppOpsController mAppOpsController; private final Handler mMainHandler; - private NotifCollection mNotifCollection; + private NotifPipeline mNotifPipeline; @Inject public ForegroundCoordinator( @@ -70,20 +69,20 @@ public class ForegroundCoordinator implements Coordinator { } @Override - public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) { - mNotifCollection = notifCollection; + public void attach(NotifPipeline pipeline) { + mNotifPipeline = pipeline; // extend the lifetime of foreground notification services to show for at least 5 seconds - mNotifCollection.addNotificationLifetimeExtender(mForegroundLifetimeExtender); + mNotifPipeline.addNotificationLifetimeExtender(mForegroundLifetimeExtender); // listen for new notifications to add appOps - mNotifCollection.addCollectionListener(mNotifCollectionListener); + mNotifPipeline.addCollectionListener(mNotifCollectionListener); // when appOps change, update any relevant notifications to update appOps for mAppOpsController.addCallback(ForegroundServiceController.APP_OPS, this::onAppOpsChanged); // filter out foreground service notifications that aren't necessary anymore - notifListBuilder.addPreGroupFilter(mNotifFilter); + mNotifPipeline.addPreGroupFilter(mNotifFilter); } /** @@ -230,7 +229,7 @@ public class ForegroundCoordinator implements Coordinator { } private NotificationEntry findNotificationEntryWithKey(String key) { - for (NotificationEntry entry : mNotifCollection.getNotifs()) { + for (NotificationEntry entry : mNotifPipeline.getActiveNotifs()) { if (entry.getKey().equals(key)) { return entry; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java index db107f531e9e..a26ee5450d60 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java @@ -39,9 +39,8 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.ListEntry; -import com.android.systemui.statusbar.notification.collection.NotifCollection; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -86,9 +85,9 @@ public class KeyguardCoordinator implements Coordinator { } @Override - public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) { + public void attach(NotifPipeline pipeline) { setupInvalidateNotifListCallbacks(); - notifListBuilder.addPreRenderFilter(mNotifFilter); + pipeline.addPreRenderFilter(mNotifFilter); } private final NotifFilter mNotifFilter = new NotifFilter(TAG) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java index 2436bb9f82f0..8d0dd5b111ba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java @@ -16,13 +16,14 @@ package com.android.systemui.statusbar.notification.collection.coordinator; +import com.android.systemui.DumpController; import com.android.systemui.Dumpable; import com.android.systemui.statusbar.FeatureFlags; -import com.android.systemui.statusbar.notification.collection.NotifCollection; -import com.android.systemui.statusbar.notification.collection.NotifCollectionListener; -import com.android.systemui.statusbar.notification.collection.NotifLifetimeExtender; -import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -33,25 +34,28 @@ import javax.inject.Inject; import javax.inject.Singleton; /** - * Handles the attachment of the {@link NotifListBuilder} and {@link NotifCollection} to the - * {@link Coordinator}s, so that the Coordinators can register their respective callbacks. + * Handles the attachment of {@link Coordinator}s to the {@link NotifPipeline} so that the + * Coordinators can register their respective callbacks. */ @Singleton public class NotifCoordinators implements Dumpable { private static final String TAG = "NotifCoordinators"; private final List<Coordinator> mCoordinators = new ArrayList<>(); - + private final List<NotifSection> mOrderedSections = new ArrayList<>(); /** * Creates all the coordinators. */ @Inject public NotifCoordinators( + DumpController dumpController, FeatureFlags featureFlags, KeyguardCoordinator keyguardCoordinator, RankingCoordinator rankingCoordinator, ForegroundCoordinator foregroundCoordinator, DeviceProvisionedCoordinator deviceProvisionedCoordinator, PreparationCoordinator preparationCoordinator) { + dumpController.registerDumpable(TAG, this); + mCoordinators.add(keyguardCoordinator); mCoordinators.add(rankingCoordinator); mCoordinators.add(foregroundCoordinator); @@ -60,18 +64,25 @@ public class NotifCoordinators implements Dumpable { mCoordinators.add(preparationCoordinator); } // TODO: add new Coordinators here! (b/145134683, b/112656837) + + // TODO: add the sections in a particular ORDER (HeadsUp < People < Alerting) + for (Coordinator c : mCoordinators) { + if (c.getSection() != null) { + mOrderedSections.add(c.getSection()); + } + } } /** - * Sends the initialized notifListBuilder and notifCollection to each - * coordinator to indicate the notifListBuilder is ready to accept {@link Pluggable}s - * and the notifCollection is ready to accept {@link NotifCollectionListener}s and - * {@link NotifLifetimeExtender}s. + * Sends the pipeline to each coordinator when the pipeline is ready to accept + * {@link Pluggable}s, {@link NotifCollectionListener}s and {@link NotifLifetimeExtender}s. */ - public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) { + public void attach(NotifPipeline pipeline) { for (Coordinator c : mCoordinators) { - c.attach(notifCollection, notifListBuilder); + c.attach(pipeline); } + + pipeline.setSections(mOrderedSections); } @Override @@ -81,5 +92,9 @@ public class NotifCoordinators implements Dumpable { for (Coordinator c : mCoordinators) { pw.println("\t" + c.getClass()); } + + for (NotifSection s : mOrderedSections) { + pw.println("\t" + s.getName()); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java index a14f0e1cf631..20c9cbc8790d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java @@ -16,13 +16,12 @@ package com.android.systemui.statusbar.notification.collection.coordinator; -import com.android.systemui.statusbar.notification.collection.NotifCollection; -import com.android.systemui.statusbar.notification.collection.NotifCollectionListener; -import com.android.systemui.statusbar.notification.collection.NotifInflater; import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; +import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.logging.NotifEvent; import com.android.systemui.statusbar.notification.logging.NotifLog; @@ -55,10 +54,10 @@ public class PreparationCoordinator implements Coordinator { } @Override - public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) { - notifCollection.addCollectionListener(mNotifCollectionListener); - notifListBuilder.addPreRenderFilter(mNotifInflationErrorFilter); - notifListBuilder.addPreRenderFilter(mNotifInflatingFilter); + public void attach(NotifPipeline pipeline) { + pipeline.addCollectionListener(mNotifCollectionListener); + pipeline.addPreRenderFilter(mNotifInflationErrorFilter); + pipeline.addPreRenderFilter(mNotifInflatingFilter); } private final NotifCollectionListener mNotifCollectionListener = new NotifCollectionListener() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java index 0751aa814215..7e9e76096873 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java @@ -17,9 +17,8 @@ package com.android.systemui.statusbar.notification.collection.coordinator; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.statusbar.notification.collection.NotifCollection; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import javax.inject.Inject; @@ -43,10 +42,10 @@ public class RankingCoordinator implements Coordinator { } @Override - public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) { + public void attach(NotifPipeline pipeline) { mStatusBarStateController.addCallback(mStatusBarStateCallback); - notifListBuilder.addPreGroupFilter(mNotifFilter); + pipeline.addPreGroupFilter(mNotifFilter); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.java index fc04827a9d6a..ea0ece444a67 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.java @@ -14,7 +14,8 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.collection; +package com.android.systemui.statusbar.notification.collection.inflation; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.coordinator.PreparationCoordinator; /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java index 7504e863ca73..3f500644b184 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 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,13 +14,14 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.collection; +package com.android.systemui.statusbar.notification.collection.inflation; import android.annotation.Nullable; import com.android.systemui.statusbar.NotificationUiAdjustment; import com.android.systemui.statusbar.notification.InflationException; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; /** * Used by the {@link NotificationEntryManager}. When notifications are added or updated, the binder diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java index 80b5b8af28ac..5cd3e9411b08 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 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,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.collection; +package com.android.systemui.statusbar.notification.collection.inflation; import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME; import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT; @@ -39,7 +39,11 @@ import com.android.systemui.statusbar.NotificationUiAdjustment; import com.android.systemui.statusbar.notification.InflationException; import com.android.systemui.statusbar.notification.NotificationClicker; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.logging.NotificationLogger; +import com.android.systemui.statusbar.notification.people.NotificationPersonExtractorPluginBoundary; +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifierImpl; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder; @@ -48,6 +52,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationListContain import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.statusbar.policy.ExtensionControllerImpl; import com.android.systemui.statusbar.policy.HeadsUpManager; import java.util.Objects; @@ -65,7 +70,6 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { private final NotificationGroupManager mGroupManager; private final NotificationGutsManager mGutsManager; private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider; - private final Context mContext; private final NotificationRowContentBinder mRowContentBinder; private final NotificationMessagingUtil mMessagingUtil; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/FakePipelineConsumer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/FakePipelineConsumer.java index 986ee17cc906..15f312dbdc00 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/FakePipelineConsumer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/FakePipelineConsumer.java @@ -19,8 +19,8 @@ package com.android.systemui.statusbar.notification.collection.init; import com.android.systemui.Dumpable; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.ListEntry; -import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.ShadeListBuilder; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -36,7 +36,7 @@ public class FakePipelineConsumer implements Dumpable { private List<ListEntry> mEntries = Collections.emptyList(); /** Attach the consumer to the pipeline. */ - public void attach(NotifListBuilderImpl listBuilder) { + public void attach(ShadeListBuilder listBuilder) { listBuilder.setOnRenderListListener(this::onBuildComplete); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NewNotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java index 8d3d0ff43deb..959b00211c63 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NewNotifPipeline.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java @@ -24,10 +24,11 @@ import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl; -import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl; -import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; +import com.android.systemui.statusbar.notification.collection.ShadeListBuilder; +import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer; import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinators; -import com.android.systemui.statusbar.notification.collection.notifcollection.GroupCoalescer; +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -39,10 +40,11 @@ import javax.inject.Singleton; * Initialization code for the new notification pipeline. */ @Singleton -public class NewNotifPipeline implements Dumpable { +public class NotifPipelineInitializer implements Dumpable { + private final NotifPipeline mPipelineWrapper; private final GroupCoalescer mGroupCoalescer; private final NotifCollection mNotifCollection; - private final NotifListBuilderImpl mNotifPipeline; + private final ShadeListBuilder mListBuilder; private final NotifCoordinators mNotifPluggableCoordinators; private final NotifInflaterImpl mNotifInflater; private final DumpController mDumpController; @@ -51,17 +53,19 @@ public class NewNotifPipeline implements Dumpable { private final FakePipelineConsumer mFakePipelineConsumer = new FakePipelineConsumer(); @Inject - public NewNotifPipeline( + public NotifPipelineInitializer( + NotifPipeline pipelineWrapper, GroupCoalescer groupCoalescer, NotifCollection notifCollection, - NotifListBuilderImpl notifPipeline, + ShadeListBuilder listBuilder, NotifCoordinators notifCoordinators, NotifInflaterImpl notifInflater, DumpController dumpController, FeatureFlags featureFlags) { + mPipelineWrapper = pipelineWrapper; mGroupCoalescer = groupCoalescer; mNotifCollection = notifCollection; - mNotifPipeline = notifPipeline; + mListBuilder = listBuilder; mNotifPluggableCoordinators = notifCoordinators; mDumpController = dumpController; mNotifInflater = notifInflater; @@ -81,11 +85,11 @@ public class NewNotifPipeline implements Dumpable { } // Wire up coordinators - mFakePipelineConsumer.attach(mNotifPipeline); - mNotifPluggableCoordinators.attach(mNotifCollection, mNotifPipeline); + mNotifPluggableCoordinators.attach(mPipelineWrapper); // Wire up pipeline - mNotifPipeline.attach(mNotifCollection); + mFakePipelineConsumer.attach(mListBuilder); + mListBuilder.attach(mNotifCollection); mNotifCollection.attach(mGroupCoalescer); mGroupCoalescer.attach(notificationService); @@ -99,5 +103,5 @@ public class NewNotifPipeline implements Dumpable { mGroupCoalescer.dump(fd, pw, args); } - private static final String TAG = "NewNotifPipeline"; + private static final String TAG = "NotifPipeline"; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifListBuilder.java deleted file mode 100644 index 758092417ae0..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifListBuilder.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.notification.collection.listbuilder; - -import com.android.systemui.statusbar.notification.collection.ListEntry; -import com.android.systemui.statusbar.notification.collection.NotifCollection; -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator; -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.SectionsProvider; - -import java.util.List; - -/** - * The system that constructs the current "notification list", the list of notifications that are - * currently being displayed to the user. - * - * The pipeline proceeds through a series of stages in order to produce the final list (see below). - * Each stage exposes hooks and listeners for other code to participate. - * - * This list differs from the canonical one we receive from system server in a few ways: - * - Filtered: Some notifications are filtered out. For example, we filter out notifications whose - * views haven't been inflated yet. We also filter out some notifications if we're on the lock - * screen. To participate, see {@link #addFilter(NotifFilter)}. - * - Grouped: Notifications that are part of the same group are clustered together into a single - * GroupEntry. These groups are then transformed in order to remove children or completely split - * them apart. To participate, see {@link #addPromoter(NotifPromoter)}. - * - Sorted: All top-level notifications are sorted. To participate, see - * {@link #setSectionsProvider(SectionsProvider)} and {@link #setComparators(List)} - * - * The exact order of all hooks is as follows: - * 0. Collection listeners are fired (see {@link NotifCollection}). - * 1. NotifFilters are called on each notification currently in NotifCollection. - * 2. Initial grouping is performed (NotificationEntries will have their parents set - * appropriately). - * 3. OnBeforeTransformGroupListeners are fired - * 4. NotifPromoters are called on each notification with a parent - * 5. OnBeforeSortListeners are fired - * 6. SectionsProvider is called on each top-level entry in the list - * 7. The top-level entries are sorted using the provided NotifComparators (plus some additional - * built-in logic). - * 8. OnBeforeRenderListListeners are fired - * 9. The list is handed off to the view layer to be rendered. - */ -public interface NotifListBuilder { - - /** - * Registers a filter with the pipeline before grouping, promoting and sorting occurs. Filters - * are called on each notification in the order that they were registered. If any filter - * returns true, the notification is removed from the pipeline (and no other filters are - * called on that notif). - */ - void addPreGroupFilter(NotifFilter filter); - - /** - * Registers a promoter with the pipeline. Promoters are able to promote child notifications to - * top-level, i.e. move a notification that would be a child of a group and make it appear - * ungrouped. Promoters are called on each child notification in the order that they are - * registered. If any promoter returns true, the notification is removed from the group (and no - * other promoters are called on it). - */ - void addPromoter(NotifPromoter promoter); - - /** - * Assigns sections to each top-level entry, where a section is simply an integer. Sections are - * the primary metric by which top-level entries are sorted; NotifComparators are only consulted - * when two entries are in the same section. The pipeline doesn't assign any particular meaning - * to section IDs -- from it's perspective they're just numbers and it sorts them by a simple - * numerical comparison. - */ - void setSectionsProvider(SectionsProvider provider); - - /** - * Comparators that are used to sort top-level entries that share the same section. The - * comparators are executed in order until one of them returns a non-zero result. If all return - * zero, the pipeline falls back to sorting by rank (and, failing that, Notification.when). - */ - void setComparators(List<NotifComparator> comparators); - - /** - * Registers a filter with the pipeline to filter right before rendering the list (after - * pre-group filtering, grouping, promoting and sorting occurs). Filters are - * called on each notification in the order that they were registered. If any filter returns - * true, the notification is removed from the pipeline (and no other filters are called on that - * notif). - */ - void addPreRenderFilter(NotifFilter filter); - - /** - * Called after notifications have been filtered and after the initial grouping has been - * performed but before NotifPromoters have had a chance to promote children out of groups. - */ - void addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener listener); - - /** - * Called after notifs have been filtered and groups have been determined but before sections - * have been determined or the notifs have been sorted. - */ - void addOnBeforeSortListener(OnBeforeSortListener listener); - - /** - * Called at the end of the pipeline after the notif list has been finalized but before it has - * been handed off to the view layer. - */ - void addOnBeforeRenderListListener(OnBeforeRenderListListener listener); - - /** - * Returns a read-only view in to the current notification list. If this method is called - * during pipeline execution it will return the current state of the list, which will likely - * be only partially-generated. - */ - List<ListEntry> getActiveNotifs(); -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeRenderListListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeRenderListListener.java index f6ca12d83fdd..44a27a4b546a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeRenderListListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeRenderListListener.java @@ -17,10 +17,11 @@ package com.android.systemui.statusbar.notification.collection.listbuilder; import com.android.systemui.statusbar.notification.collection.ListEntry; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import java.util.List; -/** See {@link NotifListBuilder#addOnBeforeRenderListListener(OnBeforeRenderListListener)} */ +/** See {@link NotifPipeline#addOnBeforeRenderListListener(OnBeforeRenderListListener)} */ public interface OnBeforeRenderListListener { /** * Called at the end of the pipeline after the notif list has been finalized but before it has diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeSortListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeSortListener.java index 7be7ac03e1f1..56cfe5cb3716 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeSortListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeSortListener.java @@ -17,10 +17,11 @@ package com.android.systemui.statusbar.notification.collection.listbuilder; import com.android.systemui.statusbar.notification.collection.ListEntry; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import java.util.List; -/** See {@link NotifListBuilder#addOnBeforeSortListener(OnBeforeSortListener)} */ +/** See {@link NotifPipeline#addOnBeforeSortListener(OnBeforeSortListener)} */ public interface OnBeforeSortListener { /** * Called after the notif list has been filtered and grouped but before sections have been diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java index d7a081510655..0dc4df0da066 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java @@ -17,13 +17,14 @@ package com.android.systemui.statusbar.notification.collection.listbuilder; import com.android.systemui.statusbar.notification.collection.ListEntry; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; import java.util.List; /** * See - * {@link NotifListBuilder#addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener)} + * {@link NotifPipeline#addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener)} */ public interface OnBeforeTransformGroupsListener { /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java index 084d03810ad9..1897ba2319ac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java @@ -18,13 +18,13 @@ package com.android.systemui.statusbar.notification.collection.listbuilder; import android.annotation.IntDef; -import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl; +import com.android.systemui.statusbar.notification.collection.ShadeListBuilder; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** - * Used by {@link NotifListBuilderImpl} to track its internal state machine. + * Used by {@link ShadeListBuilder} to track its internal state machine. */ public class PipelineState { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java index a191c830537d..0d150edee128 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java @@ -17,13 +17,13 @@ package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable; import com.android.systemui.statusbar.notification.collection.ListEntry; -import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import java.util.Comparator; import java.util.List; /** - * Pluggable for participating in notif sorting. See {@link NotifListBuilder#setComparators(List)}. + * Pluggable for participating in notif sorting. See {@link NotifPipeline#setComparators(List)}. */ public abstract class NotifComparator extends Pluggable<NotifComparator> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java index e6189edc2f3b..8f575cdd8918 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java @@ -16,12 +16,12 @@ package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; /** * Pluggable for participating in notif filtering. - * See {@link NotifListBuilder#addPreGroupFilter} and {@link NotifListBuilder#addPreRenderFilter}. + * See {@link NotifPipeline#addPreGroupFilter} and {@link NotifPipeline#addPreRenderFilter}. */ public abstract class NotifFilter extends Pluggable<NotifFilter> { protected NotifFilter(String name) { @@ -35,9 +35,9 @@ public abstract class NotifFilter extends Pluggable<NotifFilter> { * however. If another filter returns true before yours, we'll skip straight to the next notif. * * @param entry The entry in question. - * If this filter is registered via {@link NotifListBuilder#addPreGroupFilter}, + * If this filter is registered via {@link NotifPipeline#addPreGroupFilter}, * this entry will not have any grouping nor sorting information. - * If this filter is registered via {@link NotifListBuilder#addPreRenderFilter}, + * If this filter is registered via {@link NotifPipeline#addPreRenderFilter}, * this entry will have grouping and sorting information. * @param now A timestamp in SystemClock.uptimeMillis that represents "now" for the purposes of * pipeline execution. This value will be the same for all pluggable calls made diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifPromoter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifPromoter.java index 84e16f432740..5fce4462aede 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifPromoter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifPromoter.java @@ -16,13 +16,13 @@ package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; /** * Pluggable for participating in notif promotion. Notif promoters can upgrade notifications * from being children of a group to top-level notifications. See - * {@link NotifListBuilder#addPromoter(NotifPromoter)}. + * {@link NotifPipeline#addPromoter}. */ public abstract class NotifPromoter extends Pluggable<NotifPromoter> { protected NotifPromoter(String name) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/SectionsProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSection.java index 11ea85062781..fe5ba3c8e6fc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/SectionsProvider.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSection.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 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. @@ -17,21 +17,21 @@ package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable; import com.android.systemui.statusbar.notification.collection.ListEntry; +import com.android.systemui.statusbar.notification.collection.ShadeListBuilder; /** - * Interface for sorting notifications into "sections", such as a heads-upping section, people - * section, alerting section, silent section, etc. + * Pluggable for participating in notif sectioning. See {@link ShadeListBuilder#setSections}. */ -public abstract class SectionsProvider extends Pluggable<SectionsProvider> { - - protected SectionsProvider(String name) { +public abstract class NotifSection extends Pluggable<NotifSection> { + protected NotifSection(String name) { super(name); } /** - * Returns the section that this entry belongs to. A section can be any non-negative integer. - * When entries are sorted, they are first sorted by section and then by any remainining - * comparators. + * If returns true, this notification is considered within this section. + * Sectioning is performed on each top level notification each time the pipeline is run. + * However, this doesn't necessarily mean that your section will get called on each top-level + * notification. The first section to return true determines the section of the notification. */ - public abstract int getSection(ListEntry entry); + public abstract boolean isInSection(ListEntry entry); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java index f9ce197c6547..4270408d0c66 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java @@ -18,10 +18,10 @@ package com.android.systemui.statusbar.notification.collection.listbuilder.plugg import android.annotation.Nullable; -import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; /** - * Generic superclass for chunks of code that can plug into the {@link NotifListBuilder}. + * Generic superclass for chunks of code that can plug into the {@link NotifPipeline}. * * A pluggable is fundamentally three things: * 1. A name (for debugging purposes) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/CollectionReadyForBuildListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java index 87aaea007e65..4023474bf6a7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/CollectionReadyForBuildListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 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,7 +14,9 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.collection; +package com.android.systemui.statusbar.notification.collection.notifcollection; + +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import java.util.Collection; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/DismissedByUserStats.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/DismissedByUserStats.java index ecce6ea1b211..b2686864cc43 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/DismissedByUserStats.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/DismissedByUserStats.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 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,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.collection; +package com.android.systemui.statusbar.notification.collection.notifcollection; import android.service.notification.NotificationStats.DismissalSentiment; import android.service.notification.NotificationStats.DismissalSurface; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java index 032620e14336..9cbc7d7efa66 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 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,9 +14,11 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.collection; +package com.android.systemui.statusbar.notification.collection.notifcollection; +import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; /** * Listener interface for {@link NotifCollection}. @@ -36,7 +38,9 @@ public interface NotifCollectionListener { } /** - * Called immediately after a notification has been removed from the collection. + * Called whenever a notification is retracted by system server. This method is not called + * immediately after a user dismisses a notification: we wait until we receive confirmation from + * system server before considering the notification removed. */ default void onEntryRemoved( NotificationEntry entry, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java index 2c7b13866c10..05f5ea85bd4d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLifetimeExtender.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 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,9 +14,11 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.collection; +package com.android.systemui.statusbar.notification.collection.notifcollection; +import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; /** * A way for other code to temporarily extend the lifetime of a notification after it has been diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java index 3cc5e62c0bda..ccd7fa3c6837 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java @@ -99,7 +99,8 @@ public class HighPriorityProvider { } private boolean isPeopleNotification(NotificationEntry entry) { - return mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn()); + return mPeopleNotificationIdentifier.isPeopleNotification( + entry.getSbn(), entry.getChannel()); } private boolean hasUserSetImportance(NotificationEntry entry) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java index c6c36ee17873..9adceb78c249 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java @@ -22,8 +22,8 @@ import android.service.notification.StatusBarNotification; import com.android.systemui.log.RichEvent; import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; -import com.android.systemui.statusbar.notification.collection.notifcollection.GroupCoalescer; +import com.android.systemui.statusbar.notification.collection.ShadeListBuilder; +import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -67,7 +67,7 @@ public class NotifEvent extends RichEvent { } /** - * @return if this event occurred in {@link NotifListBuilder} + * @return if this event occurred in {@link ShadeListBuilder} */ static boolean isListBuilderEvent(@EventType int type) { return isBetweenInclusive(type, 0, TOTAL_LIST_BUILDER_EVENT_TYPES); @@ -94,7 +94,7 @@ public class NotifEvent extends RichEvent { LIST_BUILD_COMPLETE, PRE_GROUP_FILTER_INVALIDATED, PROMOTER_INVALIDATED, - SECTIONS_PROVIDER_INVALIDATED, + SECTION_INVALIDATED, COMPARATOR_INVALIDATED, PARENT_CHANGED, FILTER_CHANGED, @@ -132,12 +132,13 @@ public class NotifEvent extends RichEvent { "ListBuildComplete", "FilterInvalidated", "PromoterInvalidated", - "SectionsProviderInvalidated", + "SectionInvalidated", "ComparatorInvalidated", "ParentChanged", "FilterChanged", "PromoterChanged", "FinalFilterInvalidated", + "SectionerChanged", // NEM event labels: "NotifAdded", @@ -155,13 +156,14 @@ public class NotifEvent extends RichEvent { // GroupCoalescer labels: "CoalescedEvent", "EarlyBatchEmit", - "EmitEventBatch" + "EmitEventBatch", + "BatchMaxTimeout" }; private static final int TOTAL_EVENT_LABELS = EVENT_LABELS.length; /** - * Events related to {@link NotifListBuilder} + * Events related to {@link ShadeListBuilder} */ public static final int WARN = 0; public static final int ON_BUILD_LIST = 1; @@ -170,13 +172,14 @@ public class NotifEvent extends RichEvent { public static final int LIST_BUILD_COMPLETE = 4; public static final int PRE_GROUP_FILTER_INVALIDATED = 5; public static final int PROMOTER_INVALIDATED = 6; - public static final int SECTIONS_PROVIDER_INVALIDATED = 7; + public static final int SECTION_INVALIDATED = 7; public static final int COMPARATOR_INVALIDATED = 8; public static final int PARENT_CHANGED = 9; public static final int FILTER_CHANGED = 10; public static final int PROMOTER_CHANGED = 11; public static final int PRE_RENDER_FILTER_INVALIDATED = 12; - private static final int TOTAL_LIST_BUILDER_EVENT_TYPES = 13; + public static final int SECTION_CHANGED = 13; + private static final int TOTAL_LIST_BUILDER_EVENT_TYPES = 14; /** * Events related to {@link NotificationEntryManager} @@ -204,5 +207,6 @@ public class NotifEvent extends RichEvent { public static final int COALESCED_EVENT = COALESCER_EVENT_START_INDEX; public static final int EARLY_BATCH_EMIT = COALESCER_EVENT_START_INDEX + 1; public static final int EMIT_EVENT_BATCH = COALESCER_EVENT_START_INDEX + 2; + public static final int BATCH_MAX_TIMEOUT = COALESCER_EVENT_START_INDEX + 3; private static final int TOTAL_COALESCER_EVENT_TYPES = 3; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt index 78eaf3ee10a4..5c90211ca6ad 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt @@ -17,20 +17,30 @@ package com.android.systemui.statusbar.notification.people import android.app.Notification +import android.content.Context +import android.app.NotificationChannel import android.service.notification.StatusBarNotification +import android.util.FeatureFlagUtils import javax.inject.Inject import javax.inject.Singleton interface PeopleNotificationIdentifier { - fun isPeopleNotification(sbn: StatusBarNotification): Boolean + fun isPeopleNotification(sbn: StatusBarNotification, channel: NotificationChannel): Boolean } @Singleton class PeopleNotificationIdentifierImpl @Inject constructor( - private val personExtractor: NotificationPersonExtractor + private val personExtractor: NotificationPersonExtractor, + private val context: Context ) : PeopleNotificationIdentifier { - override fun isPeopleNotification(sbn: StatusBarNotification) = - sbn.notification.notificationStyle == Notification.MessagingStyle::class.java || - personExtractor.isPersonNotification(sbn) + override fun isPeopleNotification(sbn: StatusBarNotification, channel: NotificationChannel) = + ((sbn.notification.notificationStyle == Notification.MessagingStyle::class.java && + (sbn.notification.shortcutId != null || + FeatureFlagUtils.isEnabled( + context, + FeatureFlagUtils.NOTIF_CONVO_BYPASS_SHORTCUT_REQ + ))) || + personExtractor.isPersonNotification(sbn)) && + !channel.isDemoted }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index a8a35d07b3f0..b71bedaca707 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -1150,6 +1150,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mMenuRow = plugin; if (mMenuRow.shouldUseDefaultMenuItems()) { ArrayList<MenuItem> items = new ArrayList<>(); + items.add(NotificationMenuRow.createConversationItem(mContext)); items.add(NotificationMenuRow.createInfoItem(mContext)); items.add(NotificationMenuRow.createSnoozeItem(mContext)); items.add(NotificationMenuRow.createAppOpsItem(mContext)); @@ -1163,7 +1164,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override public void onPluginDisconnected(NotificationMenuRowPlugin plugin) { boolean existed = mMenuRow.getMenuView() != null; - mMenuRow = new NotificationMenuRow(mContext); // Back to default + mMenuRow = new NotificationMenuRow(mContext); if (existed) { createMenu(); } @@ -1720,6 +1721,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView */ public void reset() { mShowingPublicInitialized = false; + unDismiss(); + resetTranslation(); onHeightReset(); requestLayout(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java new file mode 100644 index 000000000000..3b106cbe10d1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java @@ -0,0 +1,642 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.notification.row; + +import static android.app.Notification.EXTRA_IS_GROUP_CONVERSATION; +import static android.app.NotificationChannel.PLACEHOLDER_CONVERSATION_ID; +import static android.app.NotificationManager.IMPORTANCE_DEFAULT; +import static android.app.NotificationManager.IMPORTANCE_LOW; +import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; +import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC; +import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED; + +import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_BUBBLE; +import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_DEMOTE; +import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_FAVORITE; +import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_HOME; +import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_MUTE; +import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_SNOOZE; + +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.app.INotificationManager; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationChannelGroup; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.LauncherApps; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; +import android.graphics.drawable.Icon; +import android.os.Bundle; +import android.os.Handler; +import android.os.Parcelable; +import android.os.RemoteException; +import android.os.UserHandle; +import android.service.notification.StatusBarNotification; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.Log; +import android.util.Slog; +import android.view.View; +import android.view.accessibility.AccessibilityEvent; +import android.widget.Button; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.Dependency; +import com.android.systemui.R; +import com.android.systemui.bubbles.BubbleController; +import com.android.systemui.statusbar.notification.VisualStabilityManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; + +import java.lang.annotation.Retention; +import java.util.Arrays; +import java.util.List; + +/** + * The guts of a conversation notification revealed when performing a long press. + */ +public class NotificationConversationInfo extends LinearLayout implements + NotificationGuts.GutsContent { + private static final String TAG = "ConversationGuts"; + + + private INotificationManager mINotificationManager; + private LauncherApps mLauncherApps; + ShortcutManager mShortcutManager; + private PackageManager mPm; + private VisualStabilityManager mVisualStabilityManager; + + private String mPackageName; + private String mAppName; + private int mAppUid; + private String mDelegatePkg; + private NotificationChannel mNotificationChannel; + private ShortcutInfo mShortcutInfo; + private String mConversationId; + private NotificationEntry mEntry; + private StatusBarNotification mSbn; + private boolean mIsDeviceProvisioned; + + private int mStartingChannelImportance; + private boolean mStartedAsBubble; + private boolean mIsBubbleable; + // TODO: remove when launcher api works + @VisibleForTesting + boolean mShowHomeScreen = false; + + private @UpdateChannelRunnable.Action int mSelectedAction = -1; + + private OnSnoozeClickListener mOnSnoozeClickListener; + private OnSettingsClickListener mOnSettingsClickListener; + private OnAppSettingsClickListener mAppSettingsClickListener; + private NotificationGuts mGutsContainer; + private BubbleController mBubbleController; + + @VisibleForTesting + boolean mSkipPost = false; + + private OnClickListener mOnBubbleClick = v -> { + mSelectedAction = ACTION_BUBBLE; + if (mStartedAsBubble) { + mBubbleController.onUserDemotedBubbleFromNotification(mEntry); + } else { + mBubbleController.onUserCreatedBubbleFromNotification(mEntry); + } + closeControls(v, true); + }; + + private OnClickListener mOnHomeClick = v -> { + mSelectedAction = ACTION_HOME; + mShortcutManager.requestPinShortcut(mShortcutInfo, null); + closeControls(v, true); + }; + + private OnClickListener mOnFavoriteClick = v -> { + mSelectedAction = ACTION_FAVORITE; + closeControls(v, true); + }; + + private OnClickListener mOnSnoozeClick = v -> { + mSelectedAction = ACTION_SNOOZE; + mOnSnoozeClickListener.onClick(v, 1); + closeControls(v, true); + }; + + private OnClickListener mOnMuteClick = v -> { + mSelectedAction = ACTION_MUTE; + closeControls(v, true); + }; + + private OnClickListener mOnDemoteClick = v -> { + mSelectedAction = ACTION_DEMOTE; + closeControls(v, true); + }; + + public NotificationConversationInfo(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public interface OnSettingsClickListener { + void onClick(View v, NotificationChannel channel, int appUid); + } + + public interface OnAppSettingsClickListener { + void onClick(View v, Intent intent); + } + + public interface OnSnoozeClickListener { + void onClick(View v, int hoursToSnooze); + } + + public void bindNotification( + ShortcutManager shortcutManager, + LauncherApps launcherApps, + PackageManager pm, + INotificationManager iNotificationManager, + VisualStabilityManager visualStabilityManager, + String pkg, + NotificationChannel notificationChannel, + NotificationEntry entry, + OnSettingsClickListener onSettingsClick, + OnAppSettingsClickListener onAppSettingsClick, + OnSnoozeClickListener onSnoozeClickListener, + boolean isDeviceProvisioned) { + mSelectedAction = -1; + mINotificationManager = iNotificationManager; + mVisualStabilityManager = visualStabilityManager; + mBubbleController = Dependency.get(BubbleController.class); + mPackageName = pkg; + mEntry = entry; + mSbn = entry.getSbn(); + mPm = pm; + mAppSettingsClickListener = onAppSettingsClick; + mAppName = mPackageName; + mOnSettingsClickListener = onSettingsClick; + mNotificationChannel = notificationChannel; + mStartingChannelImportance = mNotificationChannel.getImportance(); + mAppUid = mSbn.getUid(); + mDelegatePkg = mSbn.getOpPkg(); + mIsDeviceProvisioned = isDeviceProvisioned; + mOnSnoozeClickListener = onSnoozeClickListener; + + mShortcutManager = shortcutManager; + mLauncherApps = launcherApps; + mConversationId = mNotificationChannel.getConversationId(); + if (TextUtils.isEmpty(mNotificationChannel.getConversationId())) { + mConversationId = mSbn.getNotification().getShortcutId(); + } + // TODO: flag this when flag exists + if (TextUtils.isEmpty(mConversationId)) { + mConversationId = mSbn.getId() + mSbn.getTag() + PLACEHOLDER_CONVERSATION_ID; + } + // TODO: consider querying this earlier in the notification pipeline and passing it in + LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery() + .setPackage(mPackageName) + .setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED) + .setShortcutIds(Arrays.asList(mConversationId)); + List<ShortcutInfo> shortcuts = mLauncherApps.getShortcuts(query, mSbn.getUser()); + if (shortcuts != null && !shortcuts.isEmpty()) { + mShortcutInfo = shortcuts.get(0); + } + + mIsBubbleable = mEntry.getBubbleMetadata() != null; + mStartedAsBubble = mEntry.isBubble(); + + createConversationChannelIfNeeded(); + + bindHeader(); + bindActions(); + + } + + void createConversationChannelIfNeeded() { + // If this channel is not already a customized conversation channel, create + // a custom channel + if (TextUtils.isEmpty(mNotificationChannel.getConversationId())) { + try { + // TODO: associate this key with this channel service side so the customization + // isn't forgotten on the next update + mINotificationManager.createConversationNotificationChannelForPackage( + mPackageName, mAppUid, mNotificationChannel, mConversationId); + mNotificationChannel = mINotificationManager.getConversationNotificationChannel( + mContext.getOpPackageName(), UserHandle.getUserId(mAppUid), mPackageName, + mNotificationChannel.getId(), false, mConversationId); + + // TODO: ask LA to pin the shortcut once api exists for pinning one shortcut at a + // time + } catch (RemoteException e) { + Slog.e(TAG, "Could not create conversation channel", e); + } + } + } + + private void bindActions() { + // TODO: figure out what should happen for non-configurable channels + + Button bubble = findViewById(R.id.bubble); + bubble.setVisibility(mIsBubbleable ? VISIBLE : GONE); + bubble.setOnClickListener(mOnBubbleClick); + if (mStartedAsBubble) { + bubble.setText(R.string.notification_conversation_unbubble); + } else { + bubble.setText(R.string.notification_conversation_bubble); + } + + Button home = findViewById(R.id.home); + home.setOnClickListener(mOnHomeClick); + home.setVisibility(mShowHomeScreen && mShortcutInfo != null + && mShortcutManager.isRequestPinShortcutSupported() + ? VISIBLE : GONE); + + Button favorite = findViewById(R.id.fave); + favorite.setOnClickListener(mOnFavoriteClick); + if (mNotificationChannel.canBypassDnd()) { + favorite.setText(R.string.notification_conversation_unfavorite); + favorite.setCompoundDrawablesRelative( + mContext.getDrawable(R.drawable.ic_star), null, null, null); + } else { + favorite.setText(R.string.notification_conversation_favorite); + favorite.setCompoundDrawablesRelative( + mContext.getDrawable(R.drawable.ic_star_border), null, null, null); + } + + Button snooze = findViewById(R.id.snooze); + snooze.setOnClickListener(mOnSnoozeClick); + + Button mute = findViewById(R.id.mute); + mute.setOnClickListener(mOnMuteClick); + if (mStartingChannelImportance >= IMPORTANCE_DEFAULT + || mStartingChannelImportance == IMPORTANCE_UNSPECIFIED) { + mute.setText(R.string.notification_conversation_mute); + favorite.setCompoundDrawablesRelative( + mContext.getDrawable(R.drawable.ic_notifications_silence), null, null, null); + } else { + mute.setText(R.string.notification_conversation_unmute); + favorite.setCompoundDrawablesRelative( + mContext.getDrawable(R.drawable.ic_notifications_alert), null, null, null); + } + + ImageButton demote = findViewById(R.id.demote); + demote.setOnClickListener(mOnDemoteClick); + } + + private void bindHeader() { + bindConversationDetails(); + + // Delegate + bindDelegate(); + + // Set up app settings link (i.e. Customize) + View settingsLinkView = findViewById(R.id.app_settings); + Intent settingsIntent = getAppSettingsIntent(mPm, mPackageName, + mNotificationChannel, + mSbn.getId(), mSbn.getTag()); + if (settingsIntent != null + && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) { + settingsLinkView.setVisibility(VISIBLE); + settingsLinkView.setOnClickListener((View view) -> { + mAppSettingsClickListener.onClick(view, settingsIntent); + }); + } else { + settingsLinkView.setVisibility(View.GONE); + } + + // System Settings button. + final View settingsButton = findViewById(R.id.info); + settingsButton.setOnClickListener(getSettingsOnClickListener()); + settingsButton.setVisibility(settingsButton.hasOnClickListeners() ? VISIBLE : GONE); + } + + private OnClickListener getSettingsOnClickListener() { + if (mAppUid >= 0 && mOnSettingsClickListener != null && mIsDeviceProvisioned) { + final int appUidF = mAppUid; + return ((View view) -> { + mOnSettingsClickListener.onClick(view, mNotificationChannel, appUidF); + }); + } + return null; + } + + private void bindConversationDetails() { + final TextView channelName = findViewById(R.id.parent_channel_name); + channelName.setText(mNotificationChannel.getName()); + + bindGroup(); + bindName(); + bindPackage(); + bindIcon(); + + } + + private void bindIcon() { + ImageView image = findViewById(R.id.conversation_icon); + if (mShortcutInfo != null) { + image.setImageDrawable(mLauncherApps.getShortcutBadgedIconDrawable(mShortcutInfo, + mContext.getResources().getDisplayMetrics().densityDpi)); + } else { + // TODO: flag this behavior + if (mSbn.getNotification().extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION, false)) { + // TODO: maybe use a generic group icon, or a composite of recent senders + image.setImageDrawable(mPm.getDefaultActivityIcon()); + } else { + final List<Notification.MessagingStyle.Message> messages = + Notification.MessagingStyle.Message.getMessagesFromBundleArray( + (Parcelable[]) mSbn.getNotification().extras.get( + Notification.EXTRA_MESSAGES)); + + final Notification.MessagingStyle.Message latestMessage = + Notification.MessagingStyle.findLatestIncomingMessage(messages); + Icon personIcon = latestMessage.getSenderPerson().getIcon(); + if (personIcon != null) { + image.setImageIcon(latestMessage.getSenderPerson().getIcon()); + } else { + // TODO: choose something better + image.setImageDrawable(mPm.getDefaultActivityIcon()); + } + } + } + } + + private void bindName() { + TextView name = findViewById(R.id.name); + if (mShortcutInfo != null) { + name.setText(mShortcutInfo.getShortLabel()); + } else { + // TODO: flag this behavior + Bundle extras = mSbn.getNotification().extras; + String nameString = extras.getString(Notification.EXTRA_CONVERSATION_TITLE); + if (TextUtils.isEmpty(nameString)) { + nameString = extras.getString(Notification.EXTRA_TITLE); + } + name.setText(nameString); + } + } + + private void bindPackage() { + ApplicationInfo info; + try { + info = mPm.getApplicationInfo( + mPackageName, + PackageManager.MATCH_UNINSTALLED_PACKAGES + | PackageManager.MATCH_DISABLED_COMPONENTS + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.MATCH_DIRECT_BOOT_AWARE); + if (info != null) { + mAppName = String.valueOf(mPm.getApplicationLabel(info)); + } + } catch (PackageManager.NameNotFoundException e) { + } + ((TextView) findViewById(R.id.pkg_name)).setText(mAppName); + } + + private void bindDelegate() { + TextView delegateView = findViewById(R.id.delegate_name); + TextView dividerView = findViewById(R.id.pkg_divider); + + if (!TextUtils.equals(mPackageName, mDelegatePkg)) { + // this notification was posted by a delegate! + delegateView.setVisibility(View.VISIBLE); + dividerView.setVisibility(View.VISIBLE); + } else { + delegateView.setVisibility(View.GONE); + dividerView.setVisibility(View.GONE); + } + } + + private void bindGroup() { + // Set group information if this channel has an associated group. + CharSequence groupName = null; + if (mNotificationChannel != null && mNotificationChannel.getGroup() != null) { + try { + final NotificationChannelGroup notificationChannelGroup = + mINotificationManager.getNotificationChannelGroupForPackage( + mNotificationChannel.getGroup(), mPackageName, mAppUid); + if (notificationChannelGroup != null) { + groupName = notificationChannelGroup.getName(); + } + } catch (RemoteException e) { + } + } + TextView groupNameView = findViewById(R.id.group_name); + View groupDivider = findViewById(R.id.group_divider); + if (groupName != null) { + groupNameView.setText(groupName); + groupNameView.setVisibility(VISIBLE); + groupDivider.setVisibility(VISIBLE); + } else { + groupNameView.setVisibility(GONE); + groupDivider.setVisibility(GONE); + } + } + + @Override + public boolean post(Runnable action) { + if (mSkipPost) { + action.run(); + return true; + } else { + return super.post(action); + } + } + + @Override + public void onFinishedClosing() { + // TODO: do we need to do anything here? + } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + if (mGutsContainer != null && + event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { + if (mGutsContainer.isExposed()) { + event.getText().add(mContext.getString( + R.string.notification_channel_controls_opened_accessibility, mAppName)); + } else { + event.getText().add(mContext.getString( + R.string.notification_channel_controls_closed_accessibility, mAppName)); + } + } + } + + private Intent getAppSettingsIntent(PackageManager pm, String packageName, + NotificationChannel channel, int id, String tag) { + Intent intent = new Intent(Intent.ACTION_MAIN) + .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES) + .setPackage(packageName); + final List<ResolveInfo> resolveInfos = pm.queryIntentActivities( + intent, + PackageManager.MATCH_DEFAULT_ONLY + ); + if (resolveInfos == null || resolveInfos.size() == 0 || resolveInfos.get(0) == null) { + return null; + } + final ActivityInfo activityInfo = resolveInfos.get(0).activityInfo; + intent.setClassName(activityInfo.packageName, activityInfo.name); + if (channel != null) { + intent.putExtra(Notification.EXTRA_CHANNEL_ID, channel.getId()); + } + intent.putExtra(Notification.EXTRA_NOTIFICATION_ID, id); + intent.putExtra(Notification.EXTRA_NOTIFICATION_TAG, tag); + return intent; + } + + /** + * Closes the controls and commits the updated importance values (indirectly). + * + * <p><b>Note,</b> this will only get called once the view is dismissing. This means that the + * user does not have the ability to undo the action anymore. + */ + @VisibleForTesting + void closeControls(View v, boolean save) { + int[] parentLoc = new int[2]; + int[] targetLoc = new int[2]; + mGutsContainer.getLocationOnScreen(parentLoc); + v.getLocationOnScreen(targetLoc); + final int centerX = v.getWidth() / 2; + final int centerY = v.getHeight() / 2; + final int x = targetLoc[0] - parentLoc[0] + centerX; + final int y = targetLoc[1] - parentLoc[1] + centerY; + mGutsContainer.closeControls(x, y, save, false /* force */); + } + + @Override + public void setGutsParent(NotificationGuts guts) { + mGutsContainer = guts; + } + + @Override + public boolean willBeRemoved() { + return false; + } + + @Override + public boolean shouldBeSaved() { + return mSelectedAction > -1; + } + + @Override + public View getContentView() { + return this; + } + + @Override + public boolean handleCloseControls(boolean save, boolean force) { + if (save && mSelectedAction > -1) { + Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER)); + bgHandler.post( + new UpdateChannelRunnable(mINotificationManager, mPackageName, + mAppUid, mSelectedAction, mNotificationChannel)); + mVisualStabilityManager.temporarilyAllowReordering(); + } + return false; + } + + @Override + public int getActualHeight() { + return getHeight(); + } + + @VisibleForTesting + public boolean isAnimating() { + return false; + } + + static class UpdateChannelRunnable implements Runnable { + + @Retention(SOURCE) + @IntDef({ACTION_BUBBLE, ACTION_HOME, ACTION_FAVORITE, ACTION_SNOOZE, ACTION_MUTE, + ACTION_DEMOTE}) + private @interface Action {} + static final int ACTION_BUBBLE = 0; + static final int ACTION_HOME = 1; + static final int ACTION_FAVORITE = 2; + static final int ACTION_SNOOZE = 3; + static final int ACTION_MUTE = 4; + static final int ACTION_DEMOTE = 5; + + private final INotificationManager mINotificationManager; + private final String mAppPkg; + private final int mAppUid; + private NotificationChannel mChannelToUpdate; + private final @Action int mAction; + + public UpdateChannelRunnable(INotificationManager notificationManager, + String packageName, int appUid, @Action int action, + @NonNull NotificationChannel channelToUpdate) { + mINotificationManager = notificationManager; + mAppPkg = packageName; + mAppUid = appUid; + mChannelToUpdate = channelToUpdate; + mAction = action; + } + + @Override + public void run() { + try { + switch (mAction) { + case ACTION_BUBBLE: + mChannelToUpdate.setAllowBubbles(!mChannelToUpdate.canBubble()); + break; + case ACTION_FAVORITE: + // TODO: extend beyond DND + mChannelToUpdate.setBypassDnd(!mChannelToUpdate.canBypassDnd()); + break; + case ACTION_MUTE: + if (mChannelToUpdate.getImportance() == IMPORTANCE_UNSPECIFIED + || mChannelToUpdate.getImportance() >= IMPORTANCE_DEFAULT) { + mChannelToUpdate.setImportance(IMPORTANCE_LOW); + } else { + mChannelToUpdate.setImportance(Math.max( + mChannelToUpdate.getOriginalImportance(), IMPORTANCE_DEFAULT)); + } + break; + case ACTION_DEMOTE: + mChannelToUpdate.setDemoted(!mChannelToUpdate.isDemoted()); + break; + + } + + if (mAction != ACTION_HOME && mAction != ACTION_SNOOZE) { + mINotificationManager.updateNotificationChannelForPackage( + mAppPkg, mAppUid, mChannelToUpdate); + } + } catch (RemoteException e) { + Log.e(TAG, "Unable to update notification channel", e); + } + } + } + + @Retention(SOURCE) + @IntDef({BEHAVIOR_ALERTING, BEHAVIOR_SILENT, BEHAVIOR_BUBBLE}) + private @interface AlertingBehavior {} + private static final int BEHAVIOR_ALERTING = 0; + private static final int BEHAVIOR_SILENT = 1; + private static final int BEHAVIOR_BUBBLE = 2; +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java index 779a224ecb62..6789c814dcee 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java @@ -23,7 +23,9 @@ import android.app.INotificationManager; import android.app.NotificationChannel; import android.content.Context; import android.content.Intent; +import android.content.pm.LauncherApps; import android.content.pm.PackageManager; +import android.content.pm.ShortcutManager; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -228,6 +230,9 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx initializeAppOpsInfo(row, (AppOpsInfo) gutsView); } else if (gutsView instanceof NotificationInfo) { initializeNotificationInfo(row, (NotificationInfo) gutsView); + } else if (gutsView instanceof NotificationConversationInfo) { + initializeConversationNotificationInfo( + row, (NotificationConversationInfo) gutsView); } return true; } catch (Exception e) { @@ -339,6 +344,66 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx } /** + * Sets up the {@link NotificationConversationInfo} inside the notification row's guts. + * @param row view to set up the guts for + * @param notificationInfoView view to set up/bind within {@code row} + */ + @VisibleForTesting + void initializeConversationNotificationInfo( + final ExpandableNotificationRow row, + NotificationConversationInfo notificationInfoView) throws Exception { + NotificationGuts guts = row.getGuts(); + StatusBarNotification sbn = row.getEntry().getSbn(); + String packageName = sbn.getPackageName(); + // Settings link is only valid for notifications that specify a non-system user + NotificationConversationInfo.OnSettingsClickListener onSettingsClick = null; + UserHandle userHandle = sbn.getUser(); + PackageManager pmUser = StatusBar.getPackageManagerForUser( + mContext, userHandle.getIdentifier()); + LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class); + ShortcutManager shortcutManager = mContext.getSystemService(ShortcutManager.class); + INotificationManager iNotificationManager = INotificationManager.Stub.asInterface( + ServiceManager.getService(Context.NOTIFICATION_SERVICE)); + final NotificationConversationInfo.OnAppSettingsClickListener onAppSettingsClick = + (View v, Intent intent) -> { + mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_APP_NOTE_SETTINGS); + guts.resetFalsingCheck(); + mNotificationActivityStarter.startNotificationGutsIntent(intent, sbn.getUid(), + row); + }; + + final NotificationConversationInfo.OnSnoozeClickListener onSnoozeClickListener = + (View v, int hours) -> { + mListContainer.getSwipeActionHelper().snooze(sbn, hours); + }; + + if (!userHandle.equals(UserHandle.ALL) + || mLockscreenUserManager.getCurrentUserId() == UserHandle.USER_SYSTEM) { + onSettingsClick = (View v, NotificationChannel channel, int appUid) -> { + mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO); + guts.resetFalsingCheck(); + mOnSettingsClickListener.onSettingsClick(sbn.getKey()); + startAppNotificationSettingsActivity(packageName, appUid, channel, row); + }; + } + + notificationInfoView.bindNotification( + shortcutManager, + launcherApps, + pmUser, + iNotificationManager, + mVisualStabilityManager, + packageName, + row.getEntry().getChannel(), + row.getEntry(), + onSettingsClick, + onAppSettingsClick, + onSnoozeClickListener, + mDeviceProvisionedController.isDeviceProvisioned()); + + } + + /** * Closes guts or notification menus that might be visible and saves any changes. * * @param removeLeavebehinds true if leavebehinds (e.g. snooze) should be closed. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java index edfd1b4d3c85..212cba6a95e5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java @@ -83,7 +83,6 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl private OnMenuEventListener mMenuListener; private boolean mDismissRtl; private boolean mIsForeground; - private final boolean mIsUsingBidirectionalSwipe; private ValueAnimator mFadeAnimator; private boolean mAnimating; @@ -116,19 +115,11 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl private boolean mIsUserTouching; public NotificationMenuRow(Context context) { - //TODO: (b/131242807) not using bidirectional swipe for now - this(context, false); - } - - // Only needed for testing until we want to turn bidirectional swipe back on - @VisibleForTesting - NotificationMenuRow(Context context, boolean isUsingBidirectionalSwipe) { mContext = context; mShouldShowMenu = context.getResources().getBoolean(R.bool.config_showNotificationGear); mHandler = new Handler(Looper.getMainLooper()); mLeftMenuItems = new ArrayList<>(); mRightMenuItems = new ArrayList<>(); - mIsUsingBidirectionalSwipe = isUsingBidirectionalSwipe; } @Override @@ -269,24 +260,18 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl mSnoozeItem = createSnoozeItem(mContext); } mAppOpsItem = createAppOpsItem(mContext); - if (mIsUsingBidirectionalSwipe) { - mInfoItem = createInfoItem(mContext, - mParent.getEntry().getBucket() == NotificationSectionsManager.BUCKET_SILENT); + if (mParent.getEntry().getBucket() == NotificationSectionsManager.BUCKET_PEOPLE) { + mInfoItem = createConversationItem(mContext); } else { mInfoItem = createInfoItem(mContext); } - if (!mIsUsingBidirectionalSwipe) { - if (!isForeground && showSnooze) { - mRightMenuItems.add(mSnoozeItem); - } - mRightMenuItems.add(mInfoItem); - mRightMenuItems.add(mAppOpsItem); - mLeftMenuItems.addAll(mRightMenuItems); - } else { - ArrayList<MenuItem> menuItems = mDismissRtl ? mLeftMenuItems : mRightMenuItems; - menuItems.add(mInfoItem); + if (!isForeground && showSnooze) { + mRightMenuItems.add(mSnoozeItem); } + mRightMenuItems.add(mInfoItem); + mRightMenuItems.add(mAppOpsItem); + mLeftMenuItems.addAll(mRightMenuItems); populateMenuViews(); if (resetState) { @@ -633,12 +618,12 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl @Override public boolean shouldShowGutsOnSnapOpen() { - return mIsUsingBidirectionalSwipe; + return false; } @Override public MenuItem menuItemToExposeOnSnap() { - return mIsUsingBidirectionalSwipe ? mInfoItem : null; + return null; } @Override @@ -664,24 +649,23 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl return snooze; } - static NotificationMenuItem createInfoItem(Context context) { + static NotificationMenuItem createConversationItem(Context context) { Resources res = context.getResources(); String infoDescription = res.getString(R.string.notification_menu_gear_description); - NotificationInfo infoContent = (NotificationInfo) LayoutInflater.from(context).inflate( - R.layout.notification_info, null, false); + NotificationConversationInfo infoContent = + (NotificationConversationInfo) LayoutInflater.from(context).inflate( + R.layout.notification_conversation_info, null, false); return new NotificationMenuItem(context, infoDescription, infoContent, R.drawable.ic_settings); } - static NotificationMenuItem createInfoItem(Context context, boolean isCurrentlySilent) { + static NotificationMenuItem createInfoItem(Context context) { Resources res = context.getResources(); String infoDescription = res.getString(R.string.notification_menu_gear_description); NotificationInfo infoContent = (NotificationInfo) LayoutInflater.from(context).inflate( R.layout.notification_info, null, false); - int iconResId = isCurrentlySilent - ? R.drawable.ic_notifications_silence - : R.drawable.ic_notifications_alert; - return new NotificationMenuItem(context, infoDescription, infoContent, iconResId); + return new NotificationMenuItem(context, infoDescription, infoContent, + R.drawable.ic_settings); } static MenuItem createAppOpsItem(Context context) { 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 823dd5a73ca6..dc2d99c3e3a5 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 @@ -6207,6 +6207,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd } @Override + public void onSnooze(StatusBarNotification sbn, int hours) { + mStatusBar.setNotificationSnoozed(sbn, hours); + } + + @Override public boolean shouldDismissQuickly() { return NotificationStackScrollLayout.this.isExpanded() && mAmbientState.isFullyAwake(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java index 4845ea16020b..6c0655e7e3b3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java @@ -282,6 +282,11 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc mCallback.onSnooze(sbn, snoozeOption); } + @Override + public void snooze(StatusBarNotification sbn, int hours) { + mCallback.onSnooze(sbn, hours); + } + @VisibleForTesting protected void handleMenuCoveredOrDismissed() { View exposedMenuView = getExposedMenuView(); @@ -447,6 +452,8 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc void onSnooze(StatusBarNotification sbn, SnoozeOption snoozeOption); + void onSnooze(StatusBarNotification sbn, int hours); + void onDismiss(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java index f25f9106af7e..9840a7ba90de 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java @@ -97,6 +97,8 @@ public class EdgeBackGestureHandler implements DisplayListener, // The edge width where touch down is allowed private int mEdgeWidth; + // The bottom gesture area height + private int mBottomGestureHeight; // The slop to distinguish between horizontal and vertical motion private final float mTouchSlop; // Duration after which we consider the event as longpress. @@ -174,6 +176,8 @@ public class EdgeBackGestureHandler implements DisplayListener, public void updateCurrentUserResources(Resources res) { mEdgeWidth = res.getDimensionPixelSize( com.android.internal.R.dimen.config_backGestureInset); + mBottomGestureHeight = res.getDimensionPixelSize( + com.android.internal.R.dimen.navigation_bar_gesture_height); } /** @@ -316,6 +320,11 @@ public class EdgeBackGestureHandler implements DisplayListener, return false; } + // Disallow if we are in the bottom gesture area + if (y >= (mDisplaySize.y - mBottomGestureHeight)) { + return false; + } + // Always allow if the user is in a transient sticky immersive state if (mIsNavBarShownTransiently) { return true; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index a6842badc153..3e3ef0ccb8ce 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -27,6 +27,7 @@ import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS; import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_NAVIGATION_BARS; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON; +import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_FORCE_OPAQUE; import static com.android.systemui.recents.OverviewProxyService.OverviewProxyListener; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE; @@ -64,6 +65,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; +import android.provider.DeviceConfig; import android.provider.Settings; import android.telecom.TelecomManager; import android.text.TextUtils; @@ -157,7 +159,6 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback private int mNavigationIconHints = 0; private @TransitionMode int mNavigationBarMode; private AccessibilityManager mAccessibilityManager; - private MagnificationContentObserver mMagnificationObserver; private ContentResolver mContentResolver; private boolean mAssistantAvailable; @@ -176,6 +177,8 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback private Locale mLocale; private int mLayoutDirection; + private boolean mForceNavBarHandleOpaque; + /** @see android.view.WindowInsetsController#setSystemBarsAppearance(int) */ private @Appearance int mAppearance; @@ -228,14 +231,17 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback @Override public void onNavBarButtonAlphaChanged(float alpha, boolean animate) { ButtonDispatcher buttonDispatcher = null; + boolean forceVisible = false; if (QuickStepContract.isSwipeUpMode(mNavBarMode)) { buttonDispatcher = mNavigationBarView.getBackButton(); } else if (QuickStepContract.isGesturalMode(mNavBarMode)) { + forceVisible = mForceNavBarHandleOpaque; buttonDispatcher = mNavigationBarView.getHomeHandle(); } if (buttonDispatcher != null) { - buttonDispatcher.setVisibility(alpha > 0 ? View.VISIBLE : View.INVISIBLE); - buttonDispatcher.setAlpha(alpha, animate); + buttonDispatcher.setVisibility( + (forceVisible || alpha > 0) ? View.VISIBLE : View.INVISIBLE); + buttonDispatcher.setAlpha(forceVisible ? 1f : alpha, animate); } } }; @@ -292,6 +298,21 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback mDivider = divider; mRecentsOptional = recentsOptional; mHandler = mainHandler; + + mForceNavBarHandleOpaque = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_SYSTEMUI, + NAV_BAR_HANDLE_FORCE_OPAQUE, + /* defaultValue = */ false); + DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, mHandler::post, + new DeviceConfig.OnPropertiesChangedListener() { + @Override + public void onPropertiesChanged(DeviceConfig.Properties properties) { + if (properties.getKeyset().contains(NAV_BAR_HANDLE_FORCE_OPAQUE)) { + mForceNavBarHandleOpaque = properties.getBoolean( + NAV_BAR_HANDLE_FORCE_OPAQUE, /* defaultValue = */ false); + } + } + }); } // ----- Fragment Lifecycle Callbacks ----- @@ -303,11 +324,6 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback mWindowManager = getContext().getSystemService(WindowManager.class); mAccessibilityManager = getContext().getSystemService(AccessibilityManager.class); mContentResolver = getContext().getContentResolver(); - mMagnificationObserver = new MagnificationContentObserver( - getContext().getMainThreadHandler()); - mContentResolver.registerContentObserver(Settings.Secure.getUriFor( - Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED), false, - mMagnificationObserver, UserHandle.USER_ALL); mContentResolver.registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.ASSISTANT), false /* notifyForDescendants */, mAssistContentObserver, UserHandle.USER_ALL); @@ -329,7 +345,6 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback super.onDestroy(); mNavigationModeController.removeListener(this); mAccessibilityManagerWrapper.removeCallback(mAccessibilityListener); - mContentResolver.unregisterContentObserver(mMagnificationObserver); mContentResolver.unregisterContentObserver(mAssistContentObserver); } @@ -969,28 +984,18 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback * @param outFeedbackEnabled if non-null, sets it to true if accessibility feedback is enabled. */ public int getA11yButtonState(@Nullable boolean[] outFeedbackEnabled) { - int requestingServices = 0; - try { - if (Settings.Secure.getIntForUser(mContentResolver, - Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, - UserHandle.USER_CURRENT) == 1) { - requestingServices++; - } - } catch (Settings.SettingNotFoundException e) { - } - boolean feedbackEnabled = false; // AccessibilityManagerService resolves services for the current user since the local // AccessibilityManager is created from a Context with the INTERACT_ACROSS_USERS permission final List<AccessibilityServiceInfo> services = mAccessibilityManager.getEnabledAccessibilityServiceList( AccessibilityServiceInfo.FEEDBACK_ALL_MASK); + final List<String> a11yButtonTargets = + mAccessibilityManager.getAccessibilityShortcutTargets( + AccessibilityManager.ACCESSIBILITY_BUTTON); + final int requestingServices = a11yButtonTargets.size(); for (int i = services.size() - 1; i >= 0; --i) { AccessibilityServiceInfo info = services.get(i); - if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) { - requestingServices++; - } - if (info.feedbackType != 0 && info.feedbackType != AccessibilityServiceInfo.FEEDBACK_GENERIC) { feedbackEnabled = true; @@ -1114,18 +1119,6 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback private final AccessibilityServicesStateChangeListener mAccessibilityListener = this::updateAccessibilityServicesState; - private class MagnificationContentObserver extends ContentObserver { - - public MagnificationContentObserver(Handler handler) { - super(handler); - } - - @Override - public void onChange(boolean selfChange) { - NavigationBarFragment.this.updateAccessibilityServicesState(mAccessibilityManager); - } - } - private final Consumer<Integer> mRotationWatcher = rotation -> { if (mNavigationBarView != null && mNavigationBarView.needsReorient(rotation)) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java index e11fc1b46a5b..8c947edd9a83 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java @@ -116,7 +116,13 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State */ private void onEntryRemovedInternal(NotificationEntry removed, final StatusBarNotification sbn) { - String groupKey = getGroupKey(sbn); + onEntryRemovedInternal(removed, sbn.getGroupKey(), sbn.isGroup(), + sbn.getNotification().isGroupSummary()); + } + + private void onEntryRemovedInternal(NotificationEntry removed, String notifGroupKey, boolean + isGroup, boolean isGroupSummary) { + String groupKey = getGroupKey(removed.getKey(), notifGroupKey); final NotificationGroup group = mGroupMap.get(groupKey); if (group == null) { // When an app posts 2 different notifications as summary of the same group, then a @@ -125,7 +131,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State // the close future. See b/23676310 for reference. return; } - if (isGroupChild(sbn)) { + if (isGroupChild(removed.getKey(), isGroup, isGroupSummary)) { group.children.remove(removed.getKey()); } else { group.summary = null; @@ -229,7 +235,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State private int getNumberOfIsolatedChildren(String groupKey) { int count = 0; for (StatusBarNotification sbn : mIsolatedEntries.values()) { - if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn)) { + if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn.getKey())) { count++; } } @@ -238,31 +244,47 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State private NotificationEntry getIsolatedChild(String groupKey) { for (StatusBarNotification sbn : mIsolatedEntries.values()) { - if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn)) { + if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn.getKey())) { return mGroupMap.get(sbn.getKey()).summary; } } return null; } - public void onEntryUpdated(NotificationEntry entry, - StatusBarNotification oldNotification) { - String oldKey = oldNotification.getGroupKey(); - String newKey = entry.getSbn().getGroupKey(); - boolean groupKeysChanged = !oldKey.equals(newKey); - boolean wasGroupChild = isGroupChild(oldNotification); + /** + * Update an entry's group information + * @param entry notification entry to update + * @param oldNotification previous notification info before this update + */ + public void onEntryUpdated(NotificationEntry entry, StatusBarNotification oldNotification) { + onEntryUpdated(entry, oldNotification.getGroupKey(), oldNotification.isGroup(), + oldNotification.getNotification().isGroupSummary()); + } + + /** + * Updates an entry's group information + * @param entry notification entry to update + * @param oldGroupKey the notification's previous group key before this update + * @param oldIsGroup whether this notification was a group before this update + * @param oldIsGroupSummary whether this notification was a group summary before this update + */ + public void onEntryUpdated(NotificationEntry entry, String oldGroupKey, boolean oldIsGroup, + boolean oldIsGroupSummary) { + String newGroupKey = entry.getSbn().getGroupKey(); + boolean groupKeysChanged = !oldGroupKey.equals(newGroupKey); + boolean wasGroupChild = isGroupChild(entry.getKey(), oldIsGroup, oldIsGroupSummary); boolean isGroupChild = isGroupChild(entry.getSbn()); mIsUpdatingUnchangedGroup = !groupKeysChanged && wasGroupChild == isGroupChild; - if (mGroupMap.get(getGroupKey(oldNotification)) != null) { - onEntryRemovedInternal(entry, oldNotification); + if (mGroupMap.get(getGroupKey(entry.getKey(), oldGroupKey)) != null) { + onEntryRemovedInternal(entry, oldGroupKey, oldIsGroup, oldIsGroupSummary); } onEntryAdded(entry); mIsUpdatingUnchangedGroup = false; - if (isIsolated(entry.getSbn())) { + if (isIsolated(entry.getSbn().getKey())) { mIsolatedEntries.put(entry.getKey(), entry.getSbn()); if (groupKeysChanged) { - updateSuppression(mGroupMap.get(oldKey)); - updateSuppression(mGroupMap.get(newKey)); + updateSuppression(mGroupMap.get(oldGroupKey)); + updateSuppression(mGroupMap.get(newGroupKey)); } } else if (!wasGroupChild && isGroupChild) { onEntryBecomingChild(entry); @@ -418,10 +440,14 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State * @return the key of the notification */ public String getGroupKey(StatusBarNotification sbn) { - if (isIsolated(sbn)) { - return sbn.getKey(); + return getGroupKey(sbn.getKey(), sbn.getGroupKey()); + } + + private String getGroupKey(String key, String groupKey) { + if (isIsolated(key)) { + return key; } - return sbn.getGroupKey(); + return groupKey; } /** @return group expansion state after toggling. */ @@ -434,8 +460,8 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State return group.expanded; } - private boolean isIsolated(StatusBarNotification sbn) { - return mIsolatedEntries.containsKey(sbn.getKey()); + private boolean isIsolated(String sbnKey) { + return mIsolatedEntries.containsKey(sbnKey); } /** @@ -445,7 +471,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State * @return true if it is visually a group summary */ public boolean isGroupSummary(StatusBarNotification sbn) { - if (isIsolated(sbn)) { + if (isIsolated(sbn.getKey())) { return true; } return sbn.getNotification().isGroupSummary(); @@ -458,10 +484,14 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State * @return true if it is visually a group child */ public boolean isGroupChild(StatusBarNotification sbn) { - if (isIsolated(sbn)) { + return isGroupChild(sbn.getKey(), sbn.isGroup(), sbn.getNotification().isGroupSummary()); + } + + private boolean isGroupChild(String key, boolean isGroup, boolean isGroupSummary) { + if (isIsolated(key)) { return false; } - return sbn.isGroup() && !sbn.getNotification().isGroupSummary(); + return isGroup && !isGroupSummary; } @Override 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 189d3b6f530b..dc9cf7714e7e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -199,8 +199,8 @@ import com.android.systemui.statusbar.notification.NotificationListController; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl; -import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline; +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; +import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; @@ -364,7 +364,7 @@ public class StatusBar extends SystemUI implements DemoMode, private final HeadsUpManagerPhone mHeadsUpManager; private final DynamicPrivacyController mDynamicPrivacyController; private final BypassHeadsUpNotifier mBypassHeadsUpNotifier; - private final Lazy<NewNotifPipeline> mNewNotifPipeline; + private final Lazy<NotifPipelineInitializer> mNewNotifPipeline; private final FalsingManager mFalsingManager; private final BroadcastDispatcher mBroadcastDispatcher; private final ConfigurationController mConfigurationController; @@ -625,7 +625,7 @@ public class StatusBar extends SystemUI implements DemoMode, HeadsUpManagerPhone headsUpManagerPhone, DynamicPrivacyController dynamicPrivacyController, BypassHeadsUpNotifier bypassHeadsUpNotifier, - Lazy<NewNotifPipeline> newNotifPipeline, + Lazy<NotifPipelineInitializer> newNotifPipeline, FalsingManager falsingManager, BroadcastDispatcher broadcastDispatcher, RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler, @@ -4026,6 +4026,11 @@ public class StatusBar extends SystemUI implements DemoMode, } } + public void setNotificationSnoozed(StatusBarNotification sbn, int hoursToSnooze) { + mNotificationListener.snoozeNotification(sbn.getKey(), + hoursToSnooze * 60 * 60 * 1000); + } + @Override public void toggleSplitScreen() { toggleSplitScreenMode(-1 /* metricsDockAction */, -1 /* metricsUndockAction */); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java index be7f0a0c36f2..b4d5dadda5b8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java @@ -65,8 +65,8 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.VisualStabilityManager; -import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl; -import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline; +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; +import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; @@ -117,7 +117,7 @@ public class StatusBarModule { HeadsUpManagerPhone headsUpManagerPhone, DynamicPrivacyController dynamicPrivacyController, BypassHeadsUpNotifier bypassHeadsUpNotifier, - Lazy<NewNotifPipeline> newNotifPipeline, + Lazy<NotifPipelineInitializer> newNotifPipeline, FalsingManager falsingManager, BroadcastDispatcher broadcastDispatcher, RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index 12a65169e1df..720f22964915 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -66,7 +66,7 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl; +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java index 5916180d75ce..cca100fd48bd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java @@ -15,6 +15,9 @@ */ package com.android.systemui.statusbar.policy; +import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN; +import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS; + import android.content.Context; import android.content.Intent; import android.database.ContentObserver; @@ -23,6 +26,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.provider.Settings.Global; +import android.telephony.Annotation; import android.telephony.CellSignalStrength; import android.telephony.CellSignalStrengthCdma; import android.telephony.NetworkRegistrationInfo; @@ -34,7 +38,6 @@ import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; -import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.TelephonyIntents; @@ -50,7 +53,9 @@ import com.android.systemui.statusbar.policy.NetworkControllerImpl.SubscriptionD import java.io.PrintWriter; import java.util.BitSet; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.concurrent.Executor; import java.util.regex.Matcher; @@ -74,12 +79,14 @@ public class MobileSignalController extends SignalController< final SubscriptionInfo mSubscriptionInfo; // @VisibleForDemoMode - final SparseArray<MobileIconGroup> mNetworkToIconLookup; + final Map<String, MobileIconGroup> mNetworkToIconLookup; // Since some pieces of the phone state are interdependent we store it locally, // this could potentially become part of MobileState for simplification/complication // of code. private int mDataNetType = TelephonyManager.NETWORK_TYPE_UNKNOWN; + private boolean mCA = false; + private boolean mCAPlus = false; private int mDataState = TelephonyManager.DATA_DISCONNECTED; private ServiceState mServiceState; private SignalStrength mSignalStrength; @@ -90,9 +97,6 @@ public class MobileSignalController extends SignalController< boolean mInflateSignalStrengths = false; @VisibleForTesting boolean mIsShowingIconGracefully = false; - // Some specific carriers have 5GE network which is special LTE CA network. - private static final int NETWORK_TYPE_LTE_CA_5GE = - TelephonyManager.getAllNetworkTypes().length + 1; // TODO: Reduce number of vars passed in, if we have the NetworkController, probably don't // need listener lists anymore. @@ -103,7 +107,7 @@ public class MobileSignalController extends SignalController< super("MobileSignalController(" + info.getSubscriptionId() + ")", context, NetworkCapabilities.TRANSPORT_CELLULAR, callbackHandler, networkController); - mNetworkToIconLookup = new SparseArray<>(); + mNetworkToIconLookup = new HashMap<>(); mConfig = config; mPhone = phone; mDefaults = defaults; @@ -210,29 +214,38 @@ public class MobileSignalController extends SignalController< private void mapIconSets() { mNetworkToIconLookup.clear(); - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_0, TelephonyIcons.THREE_G); - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_A, TelephonyIcons.THREE_G); - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_B, TelephonyIcons.THREE_G); - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EHRPD, TelephonyIcons.THREE_G); - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UMTS, TelephonyIcons.THREE_G); - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_TD_SCDMA, TelephonyIcons.THREE_G); + mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_EVDO_0), + TelephonyIcons.THREE_G); + mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_EVDO_A), + TelephonyIcons.THREE_G); + mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_EVDO_B), + TelephonyIcons.THREE_G); + mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_EHRPD), + TelephonyIcons.THREE_G); + mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_UMTS), + TelephonyIcons.THREE_G); + mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_TD_SCDMA), + TelephonyIcons.THREE_G); if (!mConfig.showAtLeast3G) { - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN, + mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_UNKNOWN), TelephonyIcons.UNKNOWN); - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE, TelephonyIcons.E); - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_CDMA, TelephonyIcons.ONE_X); - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_1xRTT, TelephonyIcons.ONE_X); + mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_EDGE), + TelephonyIcons.E); + mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_CDMA), + TelephonyIcons.ONE_X); + mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_1xRTT), + TelephonyIcons.ONE_X); mDefaultIcons = TelephonyIcons.G; } else { - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN, + mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_UNKNOWN), TelephonyIcons.THREE_G); - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE, + mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_EDGE), TelephonyIcons.THREE_G); - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_CDMA, + mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_CDMA), TelephonyIcons.THREE_G); - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_1xRTT, + mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_1xRTT), TelephonyIcons.THREE_G); mDefaultIcons = TelephonyIcons.THREE_G; } @@ -247,33 +260,59 @@ public class MobileSignalController extends SignalController< hPlusGroup = TelephonyIcons.H_PLUS; } - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSDPA, hGroup); - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSUPA, hGroup); - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPA, hGroup); - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPAP, hPlusGroup); + mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_HSDPA), hGroup); + mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_HSUPA), hGroup); + mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_HSPA), hGroup); + mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_HSPAP), hPlusGroup); if (mConfig.show4gForLte) { - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.FOUR_G); + mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_LTE), + TelephonyIcons.FOUR_G); if (mConfig.hideLtePlus) { - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE_CA, + mNetworkToIconLookup.put(toIconKeyCA(TelephonyManager.NETWORK_TYPE_LTE), TelephonyIcons.FOUR_G); } else { - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE_CA, + mNetworkToIconLookup.put(toIconKeyCA(TelephonyManager.NETWORK_TYPE_LTE), TelephonyIcons.FOUR_G_PLUS); } } else { - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.LTE); + mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_LTE), + TelephonyIcons.LTE); if (mConfig.hideLtePlus) { - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE_CA, + mNetworkToIconLookup.put(toIconKeyCA(TelephonyManager.NETWORK_TYPE_LTE), TelephonyIcons.LTE); } else { - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE_CA, + mNetworkToIconLookup.put(toIconKeyCA(TelephonyManager.NETWORK_TYPE_LTE), TelephonyIcons.LTE_PLUS); } } - mNetworkToIconLookup.put(NETWORK_TYPE_LTE_CA_5GE, + mNetworkToIconLookup.put(toIconKeyCAPlus(TelephonyManager.NETWORK_TYPE_LTE), TelephonyIcons.LTE_CA_5G_E); - mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_IWLAN, TelephonyIcons.WFC); + mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_IWLAN), + TelephonyIcons.WFC); + } + + private String getIconKey() { + if (mCA) { + return toIconKeyCA(mDataNetType); + } else if (mCAPlus) { + return toIconKeyCAPlus(mDataNetType); + } else { + return toIconKey(mDataNetType); + } + } + + // Some specific carriers have 5GE network which is special CA network. + private String toIconKeyCAPlus(@Annotation.NetworkType int networkType) { + return toIconKeyCA(networkType) + "_Plus"; + } + + private String toIconKeyCA(@Annotation.NetworkType int networkType) { + return toIconKey(networkType) + "_CA"; + } + + private String toIconKey(@Annotation.NetworkType int networkType) { + return Integer.toString(networkType); } private void updateInflateSignalStrength() { @@ -520,10 +559,11 @@ public class MobileSignalController extends SignalController< nr5GIconGroup = adjustNr5GIconGroupByDisplayGraceTime(nr5GIconGroup); } + String iconKey = getIconKey(); if (nr5GIconGroup != null) { mCurrentState.iconGroup = nr5GIconGroup; - } else if (mNetworkToIconLookup.indexOfKey(mDataNetType) >= 0) { - mCurrentState.iconGroup = mNetworkToIconLookup.get(mDataNetType); + } else if (mNetworkToIconLookup.get(iconKey) != null) { + mCurrentState.iconGroup = mNetworkToIconLookup.get(iconKey); } else { mCurrentState.iconGroup = mDefaultIcons; } @@ -676,6 +716,8 @@ public class MobileSignalController extends SignalController< pw.println(" mSignalStrength=" + mSignalStrength + ","); pw.println(" mDataState=" + mDataState + ","); pw.println(" mDataNetType=" + mDataNetType + ","); + pw.println(" mCA=" + mCA + ","); + pw.println(" mCAPlus=" + mCAPlus + ","); pw.println(" mInflateSignalStrengths=" + mInflateSignalStrengths + ","); pw.println(" isDataDisabled=" + isDataDisabled() + ","); pw.println(" mIsShowingIconGracefully=" + mIsShowingIconGracefully + ","); @@ -704,7 +746,11 @@ public class MobileSignalController extends SignalController< } mServiceState = state; if (mServiceState != null) { - updateDataNetType(mServiceState.getDataNetworkType()); + NetworkRegistrationInfo regInfo = mServiceState.getNetworkRegistrationInfo( + DOMAIN_PS, TRANSPORT_TYPE_WWAN); + if (regInfo != null) { + updateDataNetType(regInfo.getAccessNetworkTechnology()); + } } updateTelephony(); } @@ -722,11 +768,13 @@ public class MobileSignalController extends SignalController< private void updateDataNetType(int networkType) { mDataNetType = networkType; + mCA = false; + mCAPlus = false; if (mDataNetType == TelephonyManager.NETWORK_TYPE_LTE) { if (isCarrierSpecificDataIcon()) { - mDataNetType = NETWORK_TYPE_LTE_CA_5GE; + mCAPlus = true; } else if (mServiceState != null && mServiceState.isUsingCarrierAggregation()) { - mDataNetType = TelephonyManager.NETWORK_TYPE_LTE_CA; + mCA = true; } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index 679fa7e2b016..6b3c5dc228bc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -22,8 +22,7 @@ import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE; import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT; import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE; - -import static com.android.internal.telephony.PhoneConstants.MAX_PHONE_COUNT_DUAL_SIM; +import static android.telephony.TelephonyManager.MODEM_COUNT_DUAL_MODEM; import android.content.BroadcastReceiver; import android.content.Context; @@ -57,7 +56,6 @@ import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.TelephonyIntents; import com.android.settingslib.net.DataUsageController; import com.android.systemui.DemoMode; @@ -547,7 +545,7 @@ public class NetworkControllerImpl extends BroadcastReceiver mReceiverHandler.post(this::handleConfigurationChanged); break; default: - int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, + int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, SubscriptionManager.INVALID_SUBSCRIPTION_ID); if (SubscriptionManager.isValidSubscriptionId(subId)) { if (mMobileSignalControllers.indexOfKey(subId) >= 0) { @@ -582,7 +580,7 @@ public class NetworkControllerImpl extends BroadcastReceiver } private void filterMobileSubscriptionInSameGroup(List<SubscriptionInfo> subscriptions) { - if (subscriptions.size() == MAX_PHONE_COUNT_DUAL_SIM) { + if (subscriptions.size() == MODEM_COUNT_DUAL_MODEM) { SubscriptionInfo info1 = subscriptions.get(0); SubscriptionInfo info2 = subscriptions.get(1); if (info1.getGroupUuid() != null && info1.getGroupUuid().equals(info2.getGroupUuid())) { diff --git a/packages/SystemUI/src/com/android/systemui/util/Assert.java b/packages/SystemUI/src/com/android/systemui/util/Assert.java index 096ac3fcee1d..f6e921e628ba 100644 --- a/packages/SystemUI/src/com/android/systemui/util/Assert.java +++ b/packages/SystemUI/src/com/android/systemui/util/Assert.java @@ -18,7 +18,7 @@ package com.android.systemui.util; import android.os.Looper; -import com.android.internal.annotations.VisibleForTesting; +import androidx.annotation.VisibleForTesting; /** * Helper providing common assertions. @@ -30,7 +30,9 @@ public class Assert { public static void isMainThread() { if (!sMainLooper.isCurrentThread()) { - throw new IllegalStateException("should be called from the main thread."); + throw new IllegalStateException("should be called from the main thread." + + " sMainLooper.threadName=" + sMainLooper.getThread().getName() + + " Thread.currentThread()=" + Thread.currentThread().getName()); } } diff --git a/packages/SystemUI/src/com/android/systemui/util/LifecycleActivity.kt b/packages/SystemUI/src/com/android/systemui/util/LifecycleActivity.kt new file mode 100644 index 000000000000..e4b7a20aab37 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/LifecycleActivity.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util + +import android.app.Activity +import android.os.Bundle +import android.os.PersistableBundle +import androidx.lifecycle.LifecycleOwner +import com.android.settingslib.core.lifecycle.Lifecycle + +open class LifecycleActivity : Activity(), LifecycleOwner { + + private val lifecycle = Lifecycle(this) + + override fun getLifecycle() = lifecycle + + override fun onCreate(savedInstanceState: Bundle?) { + lifecycle.onAttach(this) + lifecycle.onCreate(savedInstanceState) + lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_CREATE) + super.onCreate(savedInstanceState) + } + + override fun onCreate( + savedInstanceState: Bundle?, + persistentState: PersistableBundle? + ) { + lifecycle.onAttach(this) + lifecycle.onCreate(savedInstanceState) + lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_CREATE) + super.onCreate(savedInstanceState, persistentState) + } + + override fun onStart() { + lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_START) + super.onStart() + } + + override fun onResume() { + lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_RESUME) + super.onResume() + } + + override fun onPause() { + lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_PAUSE) + super.onPause() + } + + override fun onStop() { + lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_STOP) + super.onStop() + } + + override fun onDestroy() { + lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_DESTROY) + super.onDestroy() + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt index cca76bd173c7..8a1759d4d0cd 100644 --- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt +++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt @@ -27,6 +27,9 @@ import androidx.dynamicanimation.animation.SpringAnimation import androidx.dynamicanimation.animation.SpringForce import com.android.systemui.util.animation.PhysicsAnimator.Companion.getInstance import java.util.WeakHashMap +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min /** * Extension function for all objects which will return a PhysicsAnimator instance for that object. @@ -35,6 +38,15 @@ val <T : View> T.physicsAnimator: PhysicsAnimator<T> get() { return getInstance( private const val TAG = "PhysicsAnimator" +private val UNSET = -Float.MAX_VALUE + +/** + * [FlingAnimation] multiplies the friction set via [FlingAnimation.setFriction] by 4.2f, which is + * where this number comes from. We use it in [PhysicsAnimator.flingThenSpring] to calculate the + * minimum velocity for a fling to reach a certain value, given the fling's friction. + */ +private const val FLING_FRICTION_SCALAR_MULTIPLIER = 4.2f + typealias EndAction = () -> Unit /** A map of Property -> AnimationUpdate, which is provided to update listeners on each frame. */ @@ -236,6 +248,71 @@ class PhysicsAnimator<T> private constructor (val target: T) { } /** + * Flings a property using the given start velocity. If the fling animation reaches the min/max + * bounds (from the [flingConfig]) with velocity remaining, it'll overshoot it and spring back. + * + * If the object is already out of the fling bounds, it will immediately spring back within + * bounds. + * + * This is useful for animating objects that are bounded by constraints such as screen edges, + * since otherwise the fling animation would end abruptly upon reaching the min/max bounds. + * + * @param property The property to animate. + * @param startVelocity The velocity, in pixels/second, with which to start the fling. If the + * object is already outside the fling bounds, this velocity will be used as the start velocity + * of the spring that will spring it back within bounds. + * @param flingMustReachMinOrMax If true, the fling animation is guaranteed to reach either its + * minimum bound (if [startVelocity] is negative) or maximum bound (if it's positive). The + * animator will use startVelocity if it's sufficient, or add more velocity if necessary. This + * is useful when fling's deceleration-based physics are preferable to the acceleration-based + * forces used by springs - typically, when you're allowing the user to move an object somewhere + * on the screen, but it needs to be along an edge. + * @param flingConfig The configuration to use for the fling portion of the animation. + * @param springConfig The configuration to use for the spring portion of the animation. + */ + @JvmOverloads + fun flingThenSpring( + property: FloatPropertyCompat<in T>, + startVelocity: Float, + flingConfig: FlingConfig, + springConfig: SpringConfig, + flingMustReachMinOrMax: Boolean = false + ): PhysicsAnimator<T> { + val flingConfigCopy = flingConfig.copy() + val springConfigCopy = springConfig.copy() + val toAtLeast = if (startVelocity < 0) flingConfig.min else flingConfig.max + + // If the fling needs to reach min/max, calculate the velocity required to do so and use + // that if the provided start velocity is not sufficient. + if (flingMustReachMinOrMax && + toAtLeast != -Float.MAX_VALUE && toAtLeast != Float.MAX_VALUE) { + val distanceToDestination = toAtLeast - property.getValue(target) + + // The minimum velocity required for the fling to end up at the given destination, + // taking the provided fling friction value. + val velocityToReachDestination = distanceToDestination * + (flingConfig.friction * FLING_FRICTION_SCALAR_MULTIPLIER) + + // Try to use the provided start velocity, but use the required velocity to reach the + // destination if the provided velocity is insufficient. + val sufficientVelocity = + if (distanceToDestination < 0) + min(velocityToReachDestination, startVelocity) + else + max(velocityToReachDestination, startVelocity) + + flingConfigCopy.startVelocity = sufficientVelocity + springConfigCopy.finalPosition = toAtLeast + } else { + flingConfigCopy.startVelocity = startVelocity + } + + flingConfigs[property] = flingConfigCopy + springConfigs[property] = springConfigCopy + return this + } + + /** * Adds a listener that will be called whenever any property on the animated object is updated. * This will be called on every animation frame, with the current value of the animated object * and the new property values. @@ -246,7 +323,7 @@ class PhysicsAnimator<T> private constructor (val target: T) { } /** - * Adds a listener that will be called whenever a property's animation ends. This is useful if + * Adds a listener that will be called when a property stops animating. This is useful if * you care about a specific property ending, or want to use the end value/end velocity from a * particular property's animation. If you just want to run an action when all property * animations have ended, use [withEndActions]. @@ -311,6 +388,114 @@ class PhysicsAnimator<T> private constructor (val target: T) { "your test setup.") } + // Functions that will actually start the animations. These are run after we build and add + // the InternalListener, since some animations might update/end immediately and we don't + // want to miss those updates. + val animationStartActions = ArrayList<() -> Unit>() + + for (animatedProperty in getAnimatedProperties()) { + val flingConfig = flingConfigs[animatedProperty] + val springConfig = springConfigs[animatedProperty] + + // The property's current value on the object. + val currentValue = animatedProperty.getValue(target) + + // Start by checking for a fling configuration. If one is present, we're either flinging + // or flinging-then-springing. Either way, we'll want to start the fling first. + if (flingConfig != null) { + animationStartActions.add { + // When the animation is starting, adjust the min/max bounds to include the + // current value of the property, if necessary. This is required to allow a + // fling to bring an out-of-bounds object back into bounds. For example, if an + // object was dragged halfway off the left side of the screen, but then flung to + // the right, we don't want the animation to end instantly just because the + // object started out of bounds. If the fling is in the direction that would + // take it farther out of bounds, it will end instantly as expected. + flingConfig.apply { + min = min(currentValue, this.min) + max = max(currentValue, this.max) + } + + // Apply the configuration and start the animation. + getFlingAnimation(animatedProperty) + .also { flingConfig.applyToAnimation(it) } + .start() + } + } + + // Check for a spring configuration. If one is present, we're either springing, or + // flinging-then-springing. + if (springConfig != null) { + + // If there is no corresponding fling config, we're only springing. + if (flingConfig == null) { + // Apply the configuration and start the animation. + val springAnim = getSpringAnimation(animatedProperty) + springConfig.applyToAnimation(springAnim) + animationStartActions.add(springAnim::start) + } else { + // If there's a corresponding fling config, we're flinging-then-springing. Save + // the fling's original bounds so we can spring to them when the fling ends. + val flingMin = flingConfig.min + val flingMax = flingConfig.max + + // Add an end listener that will start the spring when the fling ends. + endListeners.add(0, object : EndListener<T> { + override fun onAnimationEnd( + target: T, + property: FloatPropertyCompat<in T>, + wasFling: Boolean, + canceled: Boolean, + finalValue: Float, + finalVelocity: Float, + allRelevantPropertyAnimsEnded: Boolean + ) { + // If this isn't the relevant property, it wasn't a fling, or the fling + // was explicitly cancelled, don't spring. + if (property != animatedProperty || !wasFling || canceled) { + return + } + + val endedWithVelocity = abs(finalVelocity) > 0 + + // If the object was out of bounds when the fling animation started, it + // will immediately end. In that case, we'll spring it back in bounds. + val endedOutOfBounds = finalValue !in flingMin..flingMax + + // If the fling ended either out of bounds or with remaining velocity, + // it's time to spring. + if (endedWithVelocity || endedOutOfBounds) { + springConfig.startVelocity = finalVelocity + + // If the spring's final position isn't set, this is a + // flingThenSpring where flingMustReachMinOrMax was false. We'll + // need to set the spring's final position here. + if (springConfig.finalPosition == UNSET) { + if (endedWithVelocity) { + // If the fling ended with negative velocity, that means it + // hit the min bound, so spring to that bound (and vice + // versa). + springConfig.finalPosition = + if (finalVelocity < 0) flingMin else flingMax + } else if (endedOutOfBounds) { + // If the fling ended out of bounds, spring it to the + // nearest bound. + springConfig.finalPosition = + if (finalValue < flingMin) flingMin else flingMax + } + } + + // Apply the configuration and start the spring animation. + getSpringAnimation(animatedProperty) + .also { springConfig.applyToAnimation(it) } + .start() + } + } + }) + } + } + } + // Add an internal listener that will dispatch animation events to the provided listeners. internalListeners.add(InternalListener( getAnimatedProperties(), @@ -318,24 +503,10 @@ class PhysicsAnimator<T> private constructor (val target: T) { ArrayList(endListeners), ArrayList(endActions))) - for ((property, config) in flingConfigs) { - val currentValue = property.getValue(target) - - // If the fling is already out of bounds, don't start it. - if (currentValue <= config.min || currentValue >= config.max) { - continue - } - - val flingAnim = getFlingAnimation(property) - config.applyToAnimation(flingAnim) - flingAnim.start() - } - - for ((property, config) in springConfigs) { - val springAnim = getSpringAnimation(property) - config.applyToAnimation(springAnim) - springAnim.start() - } + // Actually start the DynamicAnimations. This is delayed until after the InternalListener is + // constructed and added so that we don't miss the end listener firing for any animations + // that immediately end. + animationStartActions.forEach { it.invoke() } clearAnimator() } @@ -381,7 +552,10 @@ class PhysicsAnimator<T> private constructor (val target: T) { } anim.addEndListener { _, canceled, value, velocity -> internalListeners.removeAll { - it.onInternalAnimationEnd(property, canceled, value, velocity) } } + it.onInternalAnimationEnd( + property, canceled, value, velocity, anim is FlingAnimation) + } + } return anim } @@ -434,7 +608,8 @@ class PhysicsAnimator<T> private constructor (val target: T) { property: FloatPropertyCompat<in T>, canceled: Boolean, finalValue: Float, - finalVelocity: Float + finalVelocity: Float, + isFling: Boolean ): Boolean { // If this property animation isn't relevant to this listener, ignore it. @@ -461,7 +636,15 @@ class PhysicsAnimator<T> private constructor (val target: T) { val allEnded = !arePropertiesAnimating(properties) endListeners.forEach { - it.onAnimationEnd(target, property, canceled, finalValue, finalVelocity, allEnded) } + it.onAnimationEnd( + target, property, isFling, canceled, finalValue, finalVelocity, + allEnded) + + // Check that the end listener didn't restart this property's animation. + if (isPropertyAnimating(property)) { + return false + } + } // If all of the animations that this listener cares about have ended, run the end // actions unless the animation was canceled. @@ -495,7 +678,8 @@ class PhysicsAnimator<T> private constructor (val target: T) { /** Returns whether the given property is animating. */ fun isPropertyAnimating(property: FloatPropertyCompat<in T>): Boolean { - return springAnimations[property]?.isRunning ?: false + return springAnimations[property]?.isRunning ?: false || + flingAnimations[property]?.isRunning ?: false } /** Returns whether any of the given properties are animating. */ @@ -523,15 +707,15 @@ class PhysicsAnimator<T> private constructor (val target: T) { data class SpringConfig internal constructor( internal var stiffness: Float, internal var dampingRatio: Float, - internal var startVel: Float = 0f, - internal var finalPosition: Float = -Float.MAX_VALUE + internal var startVelocity: Float = 0f, + internal var finalPosition: Float = UNSET ) { constructor() : this(defaultSpring.stiffness, defaultSpring.dampingRatio) constructor(stiffness: Float, dampingRatio: Float) : - this(stiffness = stiffness, dampingRatio = dampingRatio, startVel = 0f) + this(stiffness = stiffness, dampingRatio = dampingRatio, startVelocity = 0f) /** Apply these configuration settings to the given SpringAnimation. */ internal fun applyToAnimation(anim: SpringAnimation) { @@ -542,7 +726,7 @@ class PhysicsAnimator<T> private constructor (val target: T) { finalPosition = this@SpringConfig.finalPosition } - if (startVel != 0f) anim.setStartVelocity(startVel) + if (startVelocity != 0f) anim.setStartVelocity(startVelocity) } } @@ -555,7 +739,7 @@ class PhysicsAnimator<T> private constructor (val target: T) { internal var friction: Float, internal var min: Float, internal var max: Float, - internal var startVel: Float + internal var startVelocity: Float ) { constructor() : this(defaultFling.friction) @@ -564,7 +748,7 @@ class PhysicsAnimator<T> private constructor (val target: T) { this(friction, defaultFling.min, defaultFling.max) constructor(friction: Float, min: Float, max: Float) : - this(friction, min, max, startVel = 0f) + this(friction, min, max, startVelocity = 0f) /** Apply these configuration settings to the given FlingAnimation. */ internal fun applyToAnimation(anim: FlingAnimation) { @@ -572,7 +756,7 @@ class PhysicsAnimator<T> private constructor (val target: T) { friction = this@FlingConfig.friction setMinValue(min) setMaxValue(max) - setStartVelocity(startVel) + setStartVelocity(startVelocity) } } } @@ -625,6 +809,10 @@ class PhysicsAnimator<T> private constructor (val target: T) { * * @param target The animated object itself. * @param property The property whose animation has just ended. + * @param wasFling Whether this property ended after a fling animation (as opposed to a + * spring animation). If this property was animated via [flingThenSpring], this will be true + * if the fling animation did not reach the min/max bounds, decelerating to a stop + * naturally. It will be false if it hit the bounds and was sprung back. * @param canceled Whether the animation was explicitly canceled before it naturally ended. * @param finalValue The final value of the animated property. * @param finalVelocity The final velocity (in pixels per second) of the ended animation. @@ -662,6 +850,7 @@ class PhysicsAnimator<T> private constructor (val target: T) { fun onAnimationEnd( target: T, property: FloatPropertyCompat<in T>, + wasFling: Boolean, canceled: Boolean, finalValue: Float, finalVelocity: Float, diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt index e86970c117cc..965decd255a0 100644 --- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt @@ -19,6 +19,7 @@ import android.os.Handler import android.os.Looper import android.util.ArrayMap import androidx.dynamicanimation.animation.FloatPropertyCompat +import com.android.systemui.util.animation.PhysicsAnimatorTestUtils.prepareForTest import java.util.ArrayDeque import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit @@ -119,6 +120,7 @@ object PhysicsAnimatorTestUtils { override fun onAnimationEnd( target: T, property: FloatPropertyCompat<in T>, + wasFling: Boolean, canceled: Boolean, finalValue: Float, finalVelocity: Float, @@ -389,8 +391,6 @@ object PhysicsAnimatorTestUtils { val unblockLatch = CountDownLatch(if (startBlocksUntilAnimationsEnd) 2 else 1) animationThreadHandler.post { - val animatedProperties = animator.getAnimatedProperties() - // Add an update listener that dispatches to any test update listeners added by // tests. animator.addUpdateListener(object : PhysicsAnimator.UpdateListener<T> { @@ -398,6 +398,10 @@ object PhysicsAnimatorTestUtils { target: T, values: ArrayMap<FloatPropertyCompat<in T>, PhysicsAnimator.AnimationUpdate> ) { + values.forEach { (property, value) -> + allUpdates.getOrPut(property, { ArrayList() }).add(value) + } + for (listener in testUpdateListeners) { listener.onAnimationUpdateForProperty(target, values) } @@ -410,6 +414,7 @@ object PhysicsAnimatorTestUtils { override fun onAnimationEnd( target: T, property: FloatPropertyCompat<in T>, + wasFling: Boolean, canceled: Boolean, finalValue: Float, finalVelocity: Float, @@ -417,7 +422,7 @@ object PhysicsAnimatorTestUtils { ) { for (listener in testEndListeners) { listener.onAnimationEnd( - target, property, canceled, finalValue, finalVelocity, + target, property, wasFling, canceled, finalValue, finalVelocity, allRelevantPropertyAnimsEnded) } @@ -432,31 +437,6 @@ object PhysicsAnimatorTestUtils { } }) - val updateListeners = ArrayList<PhysicsAnimator.UpdateListener<T>>().also { - it.add(object : PhysicsAnimator.UpdateListener<T> { - override fun onAnimationUpdateForProperty( - target: T, - values: ArrayMap<FloatPropertyCompat<in T>, - PhysicsAnimator.AnimationUpdate> - ) { - values.forEach { (property, value) -> - allUpdates.getOrPut(property, { ArrayList() }).add(value) - } - } - }) - } - - /** - * Add an internal listener at the head of the list that captures update values - * directly from DynamicAnimation. We use this to build a list of all updates so we - * can verify that InternalListener dispatches to the real listeners properly. - */ - animator.internalListeners.add(0, animator.InternalListener( - animatedProperties, - updateListeners, - ArrayList(), - ArrayList())) - animator.startInternal() unblockLatch.countDown() } diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml index bfb0e154e9f0..c51624b0994b 100644 --- a/packages/SystemUI/tests/AndroidManifest.xml +++ b/packages/SystemUI/tests/AndroidManifest.xml @@ -35,7 +35,6 @@ <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" /> <uses-permission android:name="android.permission.ACCESS_VR_MANAGER" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> - <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" /> <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" /> <uses-permission android:name="android.permission.REQUEST_NETWORK_SCORES" /> <uses-permission android:name="android.permission.CONTROL_VPN" /> diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 2e0fb3bc850d..12da00678ac2 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -57,7 +57,6 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableContext; import android.testing.TestableLooper; -import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.TelephonyIntents; import com.android.systemui.DumpController; import com.android.systemui.SysuiTestCase; @@ -524,9 +523,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { int subscription = simInited ? 1/* mock subid=1 */ : SubscriptionManager.DUMMY_SUBSCRIPTION_ID_BASE; if (data != null) intent.putExtras(data); - intent.putExtra(PhoneConstants.PHONE_NAME_KEY, "Phone"); - intent.putExtra("subscription", subscription); - intent.putExtra("slot", 0/* SLOT 1 */); + + intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subscription); + intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 0); return intent; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java index 9c9a627fa6e0..8d11b54dacb2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java @@ -48,7 +48,7 @@ import com.android.internal.messages.nano.SystemMessageProto; import com.android.systemui.appops.AppOpsController; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.collection.NotifCollection; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; @@ -71,7 +71,7 @@ public class ForegroundServiceControllerTest extends SysuiTestCase { @Mock private NotificationEntryManager mEntryManager; @Mock private AppOpsController mAppOpsController; @Mock private Handler mMainHandler; - @Mock private NotifCollection mNotifCollection; + @Mock private NotifPipeline mNotifPipeline; @Before public void setUp() throws Exception { @@ -81,7 +81,7 @@ public class ForegroundServiceControllerTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mFsc = new ForegroundServiceController(mEntryManager, mAppOpsController, mMainHandler); mListener = new ForegroundServiceNotificationListener( - mContext, mFsc, mEntryManager, mNotifCollection); + mContext, mFsc, mEntryManager, mNotifPipeline); ArgumentCaptor<NotificationEntryListener> entryListenerCaptor = ArgumentCaptor.forClass(NotificationEntryListener.class); verify(mEntryManager).addNotificationEntryListener( diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index e0b4b81c368d..c3df3f633f3b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -356,8 +356,7 @@ public class BubbleControllerTest extends SysuiTestCase { // Switch which bubble is expanded mBubbleController.selectBubble(mRow.getEntry().getKey()); - stackView.setExpandedBubble(mRow.getEntry().getKey()); - assertEquals(mRow.getEntry(), stackView.getExpandedBubble().getEntry()); + mBubbleController.expandStack(); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow.getEntry().getKey())); diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt new file mode 100644 index 000000000000..7c8c7c8f7be6 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.controller + +import android.content.ComponentName +import android.content.Context +import android.os.Binder +import android.service.controls.Control +import android.service.controls.DeviceTypes +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.time.FakeSystemClock +import dagger.Lazy +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.`when` +import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class ControlsBindingControllerTest : SysuiTestCase() { + + companion object { + fun <T> any(): T = Mockito.any<T>() + private val TEST_COMPONENT_NAME_1 = ComponentName("TEST_PKG", "TEST_CLS_1") + private val TEST_COMPONENT_NAME_2 = ComponentName("TEST_PKG", "TEST_CLS_2") + private val TEST_COMPONENT_NAME_3 = ComponentName("TEST_PKG", "TEST_CLS_3") + } + + @Mock + private lateinit var mockControlsController: ControlsController + + private val executor = FakeExecutor(FakeSystemClock()) + private lateinit var controller: ControlsBindingController + private val providers = TestableControlsBindingControllerImpl.providers + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + controller = TestableControlsBindingControllerImpl( + mContext, executor, Lazy { mockControlsController }) + } + + @After + fun tearDown() { + executor.advanceClockToLast() + executor.runAllReady() + providers.clear() + } + + @Test + fun testBindAndLoad() { + val callback: (List<Control>) -> Unit = {} + controller.bindAndLoad(TEST_COMPONENT_NAME_1, callback) + + assertEquals(1, providers.size) + val provider = providers.first() + verify(provider).maybeBindAndLoad(callback) + } + + @Test + fun testBindServices() { + controller.bindServices(listOf(TEST_COMPONENT_NAME_1, TEST_COMPONENT_NAME_2)) + executor.runAllReady() + + assertEquals(2, providers.size) + assertEquals(setOf(TEST_COMPONENT_NAME_1, TEST_COMPONENT_NAME_2), + providers.map { it.componentName }.toSet()) + providers.forEach { + verify(it).bindPermanently() + } + } + + @Test + fun testSubscribe() { + val controlInfo1 = ControlInfo(TEST_COMPONENT_NAME_1, "id_1", "", DeviceTypes.TYPE_UNKNOWN) + val controlInfo2 = ControlInfo(TEST_COMPONENT_NAME_2, "id_2", "", DeviceTypes.TYPE_UNKNOWN) + controller.bindServices(listOf(TEST_COMPONENT_NAME_3)) + + controller.subscribe(listOf(controlInfo1, controlInfo2)) + + executor.runAllReady() + + assertEquals(3, providers.size) + val provider1 = providers.first { it.componentName == TEST_COMPONENT_NAME_1 } + val provider2 = providers.first { it.componentName == TEST_COMPONENT_NAME_2 } + val provider3 = providers.first { it.componentName == TEST_COMPONENT_NAME_3 } + + verify(provider1).maybeBindAndSubscribe(listOf(controlInfo1.controlId)) + verify(provider2).maybeBindAndSubscribe(listOf(controlInfo2.controlId)) + verify(provider3, never()).maybeBindAndSubscribe(any()) + verify(provider3).unbindService() // Not needed services will be unbound + } + + @Test + fun testUnsubscribe_notRefreshing() { + controller.bindServices(listOf(TEST_COMPONENT_NAME_1, TEST_COMPONENT_NAME_2)) + controller.unsubscribe() + + executor.runAllReady() + + providers.forEach { + verify(it, never()).unsubscribe() + } + } + + @Test + fun testUnsubscribe_refreshing() { + val controlInfo1 = ControlInfo(TEST_COMPONENT_NAME_1, "id_1", "", DeviceTypes.TYPE_UNKNOWN) + val controlInfo2 = ControlInfo(TEST_COMPONENT_NAME_2, "id_2", "", DeviceTypes.TYPE_UNKNOWN) + + controller.subscribe(listOf(controlInfo1, controlInfo2)) + + controller.unsubscribe() + + executor.runAllReady() + + providers.forEach { + verify(it).unsubscribe() + } + } +} + +class TestableControlsBindingControllerImpl( + context: Context, + executor: DelayableExecutor, + lazyController: Lazy<ControlsController> +) : ControlsBindingControllerImpl(context, executor, lazyController) { + + companion object { + val providers = mutableSetOf<ControlsProviderLifecycleManager>() + } + + override fun createProviderManager(component: ComponentName): + ControlsProviderLifecycleManager { + val provider = mock(ControlsProviderLifecycleManager::class.java) + val token = Binder() + `when`(provider.componentName).thenReturn(component) + `when`(provider.token).thenReturn(token) + providers.add(provider) + return provider + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt new file mode 100644 index 000000000000..a19c299940cd --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.controller + +import android.app.PendingIntent +import android.content.ComponentName +import android.provider.Settings +import android.service.controls.Control +import android.service.controls.DeviceTypes +import android.service.controls.actions.ControlAction +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.DumpController +import com.android.systemui.SysuiTestCase +import com.android.systemui.controls.ControlStatus +import com.android.systemui.controls.ui.ControlsUiController +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.time.FakeSystemClock +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers +import org.mockito.ArgumentMatchers.eq +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.never +import org.mockito.Mockito.reset +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import java.util.Optional + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class ControlsControllerImplTest : SysuiTestCase() { + + @Mock + private lateinit var uiController: ControlsUiController + @Mock + private lateinit var bindingController: ControlsBindingController + @Mock + private lateinit var dumpController: DumpController + @Mock + private lateinit var pendingIntent: PendingIntent + @Mock + private lateinit var persistenceWrapper: ControlsFavoritePersistenceWrapper + + @Captor + private lateinit var controlInfoListCaptor: ArgumentCaptor<List<ControlInfo>> + @Captor + private lateinit var controlLoadCallbackCaptor: ArgumentCaptor<(List<Control>) -> Unit> + + private lateinit var delayableExecutor: FakeExecutor + private lateinit var controller: ControlsController + + companion object { + fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture() + fun <T : Any> safeEq(value: T): T = eq(value) ?: value + + private val TEST_COMPONENT = ComponentName("test.pkg", "test.class") + private const val TEST_CONTROL_ID = "control1" + private const val TEST_CONTROL_TITLE = "Test" + private const val TEST_DEVICE_TYPE = DeviceTypes.TYPE_AC_HEATER + private val TEST_CONTROL_INFO = ControlInfo( + TEST_COMPONENT, TEST_CONTROL_ID, TEST_CONTROL_TITLE, TEST_DEVICE_TYPE) + + private val TEST_COMPONENT_2 = ComponentName("test.pkg", "test.class.2") + private const val TEST_CONTROL_ID_2 = "control2" + private const val TEST_CONTROL_TITLE_2 = "Test 2" + private const val TEST_DEVICE_TYPE_2 = DeviceTypes.TYPE_CAMERA + private val TEST_CONTROL_INFO_2 = ControlInfo( + TEST_COMPONENT_2, TEST_CONTROL_ID_2, TEST_CONTROL_TITLE_2, TEST_DEVICE_TYPE_2) + } + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + Settings.Secure.putInt(mContext.contentResolver, + ControlsControllerImpl.CONTROLS_AVAILABLE, 1) + + delayableExecutor = FakeExecutor(FakeSystemClock()) + + controller = ControlsControllerImpl( + mContext, + delayableExecutor, + uiController, + bindingController, + Optional.of(persistenceWrapper), + dumpController + ) + assertTrue(controller.available) + } + + private fun builderFromInfo(controlInfo: ControlInfo): Control.StatelessBuilder { + return Control.StatelessBuilder(controlInfo.controlId, pendingIntent) + .setDeviceType(controlInfo.deviceType).setTitle(controlInfo.controlTitle) + } + + @Test + fun testStartWithoutFavorites() { + assertTrue(controller.getFavoriteControls().isEmpty()) + } + + @Test + fun testStartWithSavedFavorites() { + `when`(persistenceWrapper.readFavorites()).thenReturn(listOf(TEST_CONTROL_INFO)) + val controller_other = ControlsControllerImpl( + mContext, + delayableExecutor, + uiController, + bindingController, + Optional.of(persistenceWrapper), + dumpController + ) + assertEquals(listOf(TEST_CONTROL_INFO), controller_other.getFavoriteControls()) + } + + @Test + fun testAddFavorite() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + + val favorites = controller.getFavoriteControls() + assertTrue(TEST_CONTROL_INFO in favorites) + assertEquals(1, favorites.size) + } + + @Test + fun testAddMultipleFavorites() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + controller.changeFavoriteStatus(TEST_CONTROL_INFO_2, true) + + val favorites = controller.getFavoriteControls() + assertTrue(TEST_CONTROL_INFO in favorites) + assertTrue(TEST_CONTROL_INFO_2 in favorites) + assertEquals(2, favorites.size) + } + + @Test + fun testAddAndRemoveFavorite() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + controller.changeFavoriteStatus(TEST_CONTROL_INFO_2, true) + controller.changeFavoriteStatus(TEST_CONTROL_INFO, false) + + val favorites = controller.getFavoriteControls() + assertTrue(TEST_CONTROL_INFO !in favorites) + assertTrue(TEST_CONTROL_INFO_2 in favorites) + assertEquals(1, favorites.size) + } + + @Test + fun testFavoritesSavedOnAdd() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + + verify(persistenceWrapper).storeFavorites(listOf(TEST_CONTROL_INFO)) + } + + @Test + fun testFavoritesSavedOnRemove() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + reset(persistenceWrapper) + + controller.changeFavoriteStatus(TEST_CONTROL_INFO, false) + verify(persistenceWrapper).storeFavorites(emptyList()) + } + + @Test + fun testFavoritesSavedOnChange() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2) + val control = builderFromInfo(newControlInfo).build() + + controller.loadForComponent(TEST_COMPONENT) {} + + reset(persistenceWrapper) + verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT), + capture(controlLoadCallbackCaptor)) + + controlLoadCallbackCaptor.value.invoke(listOf(control)) + + verify(persistenceWrapper).storeFavorites(listOf(newControlInfo)) + } + + @Test + fun testFavoritesNotSavedOnRedundantAdd() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + + reset(persistenceWrapper) + + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + verify(persistenceWrapper, never()).storeFavorites(ArgumentMatchers.anyList()) + } + + @Test + fun testFavoritesNotSavedOnNotRemove() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO, false) + verify(persistenceWrapper, never()).storeFavorites(ArgumentMatchers.anyList()) + } + + @Test + fun testOnActionResponse() { + controller.onActionResponse(TEST_COMPONENT, TEST_CONTROL_ID, ControlAction.RESPONSE_OK) + + verify(uiController).onActionResponse(TEST_COMPONENT, TEST_CONTROL_ID, + ControlAction.RESPONSE_OK) + } + + @Test + fun testRefreshStatus() { + val list = listOf(Control.StatefulBuilder(TEST_CONTROL_ID, pendingIntent).build()) + controller.refreshStatus(TEST_COMPONENT, list) + + verify(uiController).onRefreshState(TEST_COMPONENT, list) + } + + @Test + fun testUnsubscribe() { + controller.unsubscribe() + verify(bindingController).unsubscribe() + } + + @Test + fun testSubscribeFavorites() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + controller.changeFavoriteStatus(TEST_CONTROL_INFO_2, true) + + controller.subscribeToFavorites() + + verify(bindingController).subscribe(capture(controlInfoListCaptor)) + + assertTrue(TEST_CONTROL_INFO in controlInfoListCaptor.value) + assertTrue(TEST_CONTROL_INFO_2 in controlInfoListCaptor.value) + } + + @Test + fun testLoadForComponent_noFavorites() { + var loaded = false + val control = builderFromInfo(TEST_CONTROL_INFO).build() + + controller.loadForComponent(TEST_COMPONENT) { + loaded = true + assertEquals(1, it.size) + val controlStatus = it[0] + assertEquals(ControlStatus(control, false), controlStatus) + } + + verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT), + capture(controlLoadCallbackCaptor)) + + controlLoadCallbackCaptor.value.invoke(listOf(control)) + + assertTrue(loaded) + } + + @Test + fun testLoadForComponent_favorites() { + var loaded = false + val control = builderFromInfo(TEST_CONTROL_INFO).build() + val control2 = builderFromInfo(TEST_CONTROL_INFO_2).build() + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + + controller.loadForComponent(TEST_COMPONENT) { + loaded = true + assertEquals(2, it.size) + val controlStatus = it.first { it.control.controlId == TEST_CONTROL_ID } + assertEquals(ControlStatus(control, true), controlStatus) + + val controlStatus2 = it.first { it.control.controlId == TEST_CONTROL_ID_2 } + assertEquals(ControlStatus(control2, false), controlStatus2) + } + + verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT), + capture(controlLoadCallbackCaptor)) + + controlLoadCallbackCaptor.value.invoke(listOf(control, control2)) + + assertTrue(loaded) + } + + @Test + fun testLoadForComponent_removed() { + var loaded = false + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + + controller.loadForComponent(TEST_COMPONENT) { + loaded = true + assertEquals(1, it.size) + val controlStatus = it[0] + assertEquals(TEST_CONTROL_ID, controlStatus.control.controlId) + assertTrue(controlStatus.favorite) + assertTrue(controlStatus.removed) + } + + verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT), + capture(controlLoadCallbackCaptor)) + + controlLoadCallbackCaptor.value.invoke(emptyList()) + + assertTrue(loaded) + } + + @Test + fun testFavoriteInformationModifiedOnLoad() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2) + val control = builderFromInfo(newControlInfo).build() + + controller.loadForComponent(TEST_COMPONENT) {} + + verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT), + capture(controlLoadCallbackCaptor)) + + controlLoadCallbackCaptor.value.invoke(listOf(control)) + + val favorites = controller.getFavoriteControls() + assertEquals(1, favorites.size) + assertEquals(newControlInfo, favorites[0]) + } + + @Test + fun testFavoriteInformationModifiedOnRefresh() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2) + val control = builderFromInfo(newControlInfo).build() + + controller.refreshStatus(TEST_COMPONENT, listOf(control)) + + delayableExecutor.runAllReady() + + val favorites = controller.getFavoriteControls() + assertEquals(1, favorites.size) + assertEquals(newControlInfo, favorites[0]) + } + + @Test + fun testClearFavorites() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + assertEquals(1, controller.getFavoriteControls().size) + + controller.clearFavorites() + assertTrue(controller.getFavoriteControls().isEmpty()) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt new file mode 100644 index 000000000000..c145c1f4ee30 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.controller + +import android.content.ComponentName +import android.service.controls.DeviceTypes +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.time.FakeSystemClock +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import java.io.File + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class ControlsFavoritePersistenceWrapperTest : SysuiTestCase() { + + private lateinit var file: File + + private val executor = FakeExecutor(FakeSystemClock()) + + private lateinit var wrapper: ControlsFavoritePersistenceWrapper + + @Before + fun setUp() { + file = File.createTempFile("controls_favorites", ".temp") + wrapper = ControlsFavoritePersistenceWrapper(file, executor) + } + + @After + fun tearDown() { + if (file.exists() ?: false) { + file.delete() + } + } + + @Test + fun testSaveAndRestore() { + val controlInfo1 = ControlInfo( + ComponentName.unflattenFromString("TEST_PKG/.TEST_CLS_1")!!, + "id1", "name_1", DeviceTypes.TYPE_UNKNOWN) + val controlInfo2 = ControlInfo( + ComponentName.unflattenFromString("TEST_PKG/.TEST_CLS_2")!!, + "id2", "name_2", DeviceTypes.TYPE_GENERIC_ON_OFF) + val list = listOf(controlInfo1, controlInfo2) + + wrapper.storeFavorites(list) + + executor.runAllReady() + + assertEquals(list, wrapper.readFavorites()) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt new file mode 100644 index 000000000000..556bb4092d07 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.controller + +import android.content.ComponentName +import android.service.controls.Control +import android.service.controls.IControlsProvider +import android.service.controls.IControlsProviderCallback +import android.service.controls.actions.ControlAction +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.time.FakeSystemClock +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class ControlsProviderLifecycleManagerTest : SysuiTestCase() { + + @Mock + private lateinit var serviceCallback: IControlsProviderCallback.Stub + @Mock + private lateinit var service: IControlsProvider.Stub + + private val componentName = ComponentName("test.pkg", "test.cls") + private lateinit var manager: ControlsProviderLifecycleManager + private lateinit var executor: DelayableExecutor + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + mContext.addMockService(componentName, service) + executor = FakeExecutor(FakeSystemClock()) + `when`(service.asBinder()).thenCallRealMethod() + `when`(service.queryLocalInterface(ArgumentMatchers.anyString())).thenReturn(service) + + manager = ControlsProviderLifecycleManager( + context, + executor, + serviceCallback, + componentName + ) + } + + @After + fun tearDown() { + manager.unbindService() + } + + @Test + fun testBindService() { + manager.bindPermanently() + assertTrue(mContext.isBound(componentName)) + } + + @Test + fun testUnbindService() { + manager.bindPermanently() + manager.unbindService() + assertFalse(mContext.isBound(componentName)) + } + + @Test + fun testMaybeBindAndLoad() { + val callback: (List<Control>) -> Unit = {} + manager.maybeBindAndLoad(callback) + + verify(service).load() + + assertTrue(mContext.isBound(componentName)) + assertEquals(callback, manager.lastLoadCallback) + } + + @Test + fun testMaybeUnbind_bindingAndCallback() { + manager.maybeBindAndLoad {} + + manager.maybeUnbindAndRemoveCallback() + assertFalse(mContext.isBound(componentName)) + assertNull(manager.lastLoadCallback) + } + + @Test + fun testUnsubscribe() { + manager.bindPermanently() + manager.unsubscribe() + + verify(service).unsubscribe() + } + + @Test + fun testMaybeBindAndSubscribe() { + val list = listOf("TEST_ID") + manager.maybeBindAndSubscribe(list) + + assertTrue(mContext.isBound(componentName)) + verify(service).subscribe(list) + } + + @Test + fun testMaybeBindAndAction() { + val controlId = "TEST_ID" + val action = ControlAction.UNKNOWN_ACTION + manager.maybeBindAndSendAction(controlId, action) + + assertTrue(mContext.isBound(componentName)) + verify(service).onAction(controlId, action) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapperTest.kt new file mode 100644 index 000000000000..d6993c06a3c3 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapperTest.kt @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.controller + +import android.os.RemoteException +import android.service.controls.IControlsProvider +import android.service.controls.actions.ControlAction +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.any +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class ControlsProviderServiceWrapperTest : SysuiTestCase() { + + @Mock + private lateinit var service: IControlsProvider + + private val exception = RemoteException() + + private lateinit var wrapper: ControlsProviderServiceWrapper + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + wrapper = ControlsProviderServiceWrapper(service) + } + + @Test + fun testLoad_happyPath() { + val result = wrapper.load() + + assertTrue(result) + verify(service).load() + } + + @Test + fun testLoad_error() { + `when`(service.load()).thenThrow(exception) + val result = wrapper.load() + + assertFalse(result) + } + + @Test + fun testSubscribe_happyPath() { + val list = listOf("TEST_ID") + val result = wrapper.subscribe(list) + + assertTrue(result) + verify(service).subscribe(list) + } + + @Test + fun testSubscribe_error() { + `when`(service.subscribe(any())).thenThrow(exception) + + val list = listOf("TEST_ID") + val result = wrapper.subscribe(list) + + assertFalse(result) + } + + @Test + fun testUnsubscribe_happyPath() { + val result = wrapper.unsubscribe() + + assertTrue(result) + verify(service).unsubscribe() + } + + @Test + fun testUnsubscribe_error() { + `when`(service.unsubscribe()).thenThrow(exception) + val result = wrapper.unsubscribe() + + assertFalse(result) + } + + @Test + fun testOnAction_happyPath() { + val id = "TEST_ID" + val action = ControlAction.UNKNOWN_ACTION + + val result = wrapper.onAction(id, action) + + assertTrue(result) + verify(service).onAction(id, action) + } + + @Test + fun testOnAction_error() { + `when`(service.onAction(any(), any())).thenThrow(exception) + + val id = "TEST_ID" + val action = ControlAction.UNKNOWN_ACTION + + val result = wrapper.onAction(id, action) + + assertFalse(result) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt new file mode 100644 index 000000000000..f09aab97a219 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.management + +import android.content.ComponentName +import android.content.pm.ServiceInfo +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.settingslib.applications.ServiceListing +import com.android.settingslib.widget.CandidateInfo +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.time.FakeSystemClock +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.`when` +import org.mockito.Mockito.never +import org.mockito.Mockito.reset +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class ControlsListingControllerImplTest : SysuiTestCase() { + + companion object { + private const val TEST_LABEL = "TEST_LABEL" + private const val TEST_PERMISSION = "permission" + fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture() + fun <T> any(): T = Mockito.any<T>() + } + + @Mock + private lateinit var mockSL: ServiceListing + @Mock + private lateinit var mockCallback: ControlsListingController.ControlsListingCallback + @Mock + private lateinit var mockCallbackOther: ControlsListingController.ControlsListingCallback + @Mock + private lateinit var serviceInfo: ServiceInfo + @Mock + private lateinit var componentName: ComponentName + + private val executor = FakeExecutor(FakeSystemClock()) + + private lateinit var controller: ControlsListingControllerImpl + + private var serviceListingCallbackCaptor = + ArgumentCaptor.forClass(ServiceListing.Callback::class.java) + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + `when`(serviceInfo.componentName).thenReturn(componentName) + + controller = ControlsListingControllerImpl(mContext, executor, mockSL) + verify(mockSL).addCallback(capture(serviceListingCallbackCaptor)) + } + + @After + fun tearDown() { + executor.advanceClockToLast() + executor.runAllReady() + } + + @Test + fun testNoServices_notListening() { + assertTrue(controller.getCurrentServices().isEmpty()) + } + + @Test + fun testStartListening_onFirstCallback() { + controller.addCallback(mockCallback) + executor.runAllReady() + + verify(mockSL).setListening(true) + } + + @Test + fun testStartListening_onlyOnce() { + controller.addCallback(mockCallback) + controller.addCallback(mockCallbackOther) + + executor.runAllReady() + + verify(mockSL).setListening(true) + } + + @Test + fun testStopListening_callbackRemoved() { + controller.addCallback(mockCallback) + + executor.runAllReady() + + controller.removeCallback(mockCallback) + + executor.runAllReady() + + verify(mockSL).setListening(false) + } + + @Test + fun testStopListening_notWhileRemainingCallbacks() { + controller.addCallback(mockCallback) + controller.addCallback(mockCallbackOther) + + executor.runAllReady() + + controller.removeCallback(mockCallback) + + executor.runAllReady() + + verify(mockSL, never()).setListening(false) + } + + @Test + fun testReloadOnFirstCallbackAdded() { + controller.addCallback(mockCallback) + executor.runAllReady() + + verify(mockSL).reload() + } + + @Test + fun testCallbackCalledWhenAdded() { + `when`(mockSL.reload()).then { + serviceListingCallbackCaptor.value.onServicesReloaded(emptyList()) + } + + controller.addCallback(mockCallback) + executor.runAllReady() + verify(mockCallback).onServicesUpdated(any()) + reset(mockCallback) + + controller.addCallback(mockCallbackOther) + executor.runAllReady() + verify(mockCallbackOther).onServicesUpdated(any()) + verify(mockCallback, never()).onServicesUpdated(any()) + } + + @Test + fun testCallbackGetsList() { + val list = listOf(serviceInfo) + controller.addCallback(mockCallback) + controller.addCallback(mockCallbackOther) + + @Suppress("unchecked_cast") + val captor: ArgumentCaptor<List<CandidateInfo>> = + ArgumentCaptor.forClass(List::class.java) as ArgumentCaptor<List<CandidateInfo>> + + executor.runAllReady() + reset(mockCallback) + reset(mockCallbackOther) + + serviceListingCallbackCaptor.value.onServicesReloaded(list) + + executor.runAllReady() + verify(mockCallback).onServicesUpdated(capture(captor)) + assertEquals(1, captor.value.size) + assertEquals(componentName.flattenToString(), captor.value[0].key) + + verify(mockCallbackOther).onServicesUpdated(capture(captor)) + assertEquals(1, captor.value.size) + assertEquals(componentName.flattenToString(), captor.value[0].key) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java index 1e0179dc92f1..d852fa151fd3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java @@ -78,7 +78,7 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager.Keyg import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; -import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl; +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.statusbar.notification.logging.NotificationLogger; @@ -571,7 +571,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { } private NotificationEntry createNotification() { - Notification.Builder n = new Notification.Builder(mContext, "") + Notification.Builder n = new Notification.Builder(mContext, "id") .setSmallIcon(R.drawable.ic_person) .setContentTitle("Title") .setContentText("Text"); @@ -582,6 +582,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { .setUid(TEST_UID) .setId(mId++) .setNotification(n.build()) + .setChannel(new NotificationChannel("id", "", IMPORTANCE_DEFAULT)) .setUser(new UserHandle(ActivityManager.getCurrentUser())) .build(); } @@ -616,7 +617,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { @Test public void testGetNotificationsForCurrentUser_shouldFilterNonCurrentUserNotifications() { Assert.sMainLooper = TestableLooper.get(this).getLooper(); - Notification.Builder n = new Notification.Builder(mContext, "") + Notification.Builder n = new Notification.Builder(mContext, "di") .setSmallIcon(R.drawable.ic_person) .setContentTitle("Title") .setContentText("Text"); @@ -628,6 +629,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { .setId(mId++) .setNotification(n.build()) .setUser(new UserHandle(ActivityManager.getCurrentUser())) + .setChannel(new NotificationChannel("id", "", IMPORTANCE_DEFAULT)) .build(); mEntryManager.addActiveNotificationForTest(mEntry); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt index bf84f2bc599c..29ce92074027 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt @@ -21,7 +21,7 @@ import com.android.systemui.statusbar.NotificationPresenter import com.android.systemui.statusbar.NotificationRemoteInputManager import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationRankingManager -import com.android.systemui.statusbar.notification.collection.NotificationRowBinder +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder import com.android.systemui.statusbar.notification.logging.NotifLog import com.android.systemui.statusbar.notification.stack.NotificationListContainer import com.android.systemui.statusbar.phone.HeadsUpManagerPhone diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java index 93909dc4d330..7c3665bfe6fb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java @@ -60,7 +60,8 @@ public class HighPriorityProviderTest extends SysuiTestCase { final NotificationEntry entry = new NotificationEntryBuilder() .setImportance(IMPORTANCE_HIGH) .build(); - when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(false); + when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getChannel())) + .thenReturn(false); // THEN it has high priority assertTrue(mHighPriorityProvider.isHighPriority(entry)); @@ -75,7 +76,8 @@ public class HighPriorityProviderTest extends SysuiTestCase { .setNotification(notification) .setImportance(IMPORTANCE_LOW) .build(); - when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(true); + when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getChannel())) + .thenReturn(true); // THEN it has high priority assertTrue(mHighPriorityProvider.isHighPriority(entry)); @@ -90,7 +92,8 @@ public class HighPriorityProviderTest extends SysuiTestCase { final NotificationEntry entry = new NotificationEntryBuilder() .setNotification(notification) .build(); - when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(false); + when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getChannel())) + .thenReturn(false); // THEN it has high priority assertTrue(mHighPriorityProvider.isHighPriority(entry)); @@ -106,7 +109,8 @@ public class HighPriorityProviderTest extends SysuiTestCase { .setNotification(notification) .setImportance(IMPORTANCE_LOW) .build(); - when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(false); + when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getChannel())) + .thenReturn(false); // THEN it has high priority assertTrue(mHighPriorityProvider.isHighPriority(entry)); @@ -122,7 +126,8 @@ public class HighPriorityProviderTest extends SysuiTestCase { .setNotification(notification) .setImportance(IMPORTANCE_MIN) .build(); - when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(false); + when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getChannel())) + .thenReturn(false); // THEN it does NOT have high priority assertFalse(mHighPriorityProvider.isHighPriority(entry)); @@ -144,7 +149,8 @@ public class HighPriorityProviderTest extends SysuiTestCase { .setNotification(notification) .setChannel(channel) .build(); - when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(true); + when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getChannel())) + .thenReturn(true); // THEN it does NOT have high priority assertFalse(mHighPriorityProvider.isHighPriority(entry)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java index 28feacac8c44..fe8d76923141 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java @@ -29,6 +29,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -48,13 +49,18 @@ import androidx.test.filters.SmallTest; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; +import com.android.systemui.DumpController; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.notification.collection.NoManSimulator.NotifEvent; import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason; -import com.android.systemui.statusbar.notification.collection.notifcollection.CoalescedEvent; -import com.android.systemui.statusbar.notification.collection.notifcollection.GroupCoalescer; -import com.android.systemui.statusbar.notification.collection.notifcollection.GroupCoalescer.BatchableNotificationHandler; +import com.android.systemui.statusbar.notification.collection.coalescer.CoalescedEvent; +import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer; +import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer.BatchableNotificationHandler; +import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener; +import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; import com.android.systemui.util.Assert; import org.junit.Before; @@ -99,7 +105,7 @@ public class NotifCollectionTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); Assert.sMainLooper = TestableLooper.get(this).getLooper(); - mCollection = new NotifCollection(mStatusBarService); + mCollection = new NotifCollection(mStatusBarService, mock(DumpController.class)); mCollection.attach(mGroupCoalescer); mCollection.addCollectionListener(mCollectionListener); mCollection.setBuildListener(mBuildListener); @@ -377,7 +383,7 @@ public class NotifCollectionTest extends SysuiTestCase { verify(mExtender3).shouldExtendLifetime(entry2, REASON_UNKNOWN); // THEN the entry is not removed - assertTrue(mCollection.getNotifs().contains(entry2)); + assertTrue(mCollection.getActiveNotifs().contains(entry2)); // THEN the entry properly records all extenders that returned true assertEquals(Arrays.asList(mExtender1, mExtender2), entry2.mLifetimeExtenders); @@ -398,7 +404,7 @@ public class NotifCollectionTest extends SysuiTestCase { // GIVEN a notification gets lifetime-extended by one of them mNoMan.retractNotif(notif2.sbn, REASON_APP_CANCEL); - assertTrue(mCollection.getNotifs().contains(entry2)); + assertTrue(mCollection.getActiveNotifs().contains(entry2)); clearInvocations(mExtender1, mExtender2, mExtender3); // WHEN the last active extender expires (but new ones become active) @@ -413,7 +419,7 @@ public class NotifCollectionTest extends SysuiTestCase { verify(mExtender3).shouldExtendLifetime(entry2, REASON_UNKNOWN); // THEN the entry is not removed - assertTrue(mCollection.getNotifs().contains(entry2)); + assertTrue(mCollection.getActiveNotifs().contains(entry2)); // THEN the entry properly records all extenders that returned true assertEquals(Arrays.asList(mExtender1, mExtender3), entry2.mLifetimeExtenders); @@ -435,7 +441,7 @@ public class NotifCollectionTest extends SysuiTestCase { // GIVEN a notification gets lifetime-extended by a couple of them mNoMan.retractNotif(notif2.sbn, REASON_APP_CANCEL); - assertTrue(mCollection.getNotifs().contains(entry2)); + assertTrue(mCollection.getActiveNotifs().contains(entry2)); clearInvocations(mExtender1, mExtender2, mExtender3); // WHEN one (but not all) of the extenders expires @@ -443,7 +449,7 @@ public class NotifCollectionTest extends SysuiTestCase { mExtender2.callback.onEndLifetimeExtension(mExtender2, entry2); // THEN the entry is not removed - assertTrue(mCollection.getNotifs().contains(entry2)); + assertTrue(mCollection.getActiveNotifs().contains(entry2)); // THEN we don't re-query the extenders verify(mExtender1, never()).shouldExtendLifetime(eq(entry2), anyInt()); @@ -470,7 +476,7 @@ public class NotifCollectionTest extends SysuiTestCase { // GIVEN a notification gets lifetime-extended by a couple of them mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN); - assertTrue(mCollection.getNotifs().contains(entry2)); + assertTrue(mCollection.getActiveNotifs().contains(entry2)); clearInvocations(mExtender1, mExtender2, mExtender3); // WHEN all of the active extenders expire @@ -480,7 +486,7 @@ public class NotifCollectionTest extends SysuiTestCase { mExtender1.callback.onEndLifetimeExtension(mExtender1, entry2); // THEN the entry removed - assertFalse(mCollection.getNotifs().contains(entry2)); + assertFalse(mCollection.getActiveNotifs().contains(entry2)); verify(mCollectionListener).onEntryRemoved(entry2, REASON_UNKNOWN, false); } @@ -500,7 +506,7 @@ public class NotifCollectionTest extends SysuiTestCase { // GIVEN a notification gets lifetime-extended by a couple of them mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN); - assertTrue(mCollection.getNotifs().contains(entry2)); + assertTrue(mCollection.getActiveNotifs().contains(entry2)); clearInvocations(mExtender1, mExtender2, mExtender3); // WHEN the notification is reposted @@ -511,7 +517,7 @@ public class NotifCollectionTest extends SysuiTestCase { verify(mExtender2).cancelLifetimeExtension(entry2); // THEN the notification is still present - assertTrue(mCollection.getNotifs().contains(entry2)); + assertTrue(mCollection.getActiveNotifs().contains(entry2)); } @Test(expected = IllegalStateException.class) @@ -530,7 +536,7 @@ public class NotifCollectionTest extends SysuiTestCase { // GIVEN a notification gets lifetime-extended by a couple of them mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN); - assertTrue(mCollection.getNotifs().contains(entry2)); + assertTrue(mCollection.getActiveNotifs().contains(entry2)); clearInvocations(mExtender1, mExtender2, mExtender3); // WHEN a lifetime extender makes a reentrant call during cancelLifetimeExtension() @@ -559,7 +565,7 @@ public class NotifCollectionTest extends SysuiTestCase { // GIVEN a notification gets lifetime-extended by a couple of them mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN); - assertTrue(mCollection.getNotifs().contains(entry2)); + assertTrue(mCollection.getActiveNotifs().contains(entry2)); clearInvocations(mExtender1, mExtender2, mExtender3); // WHEN the notification is reposted diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt index e27319103525..7ab4846ea066 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.collection import android.app.Notification +import android.app.NotificationChannel import android.app.NotificationManager.IMPORTANCE_DEFAULT import android.app.NotificationManager.IMPORTANCE_HIGH import android.app.NotificationManager.IMPORTANCE_LOW @@ -81,6 +82,7 @@ class NotificationRankingManagerTest : SysuiTestCase() { .setNotification( Notification.Builder(mContext, "test") .build()) + .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT)) .setUser(mContext.getUser()) .setOverrideGroupKey("") .build() @@ -94,6 +96,7 @@ class NotificationRankingManagerTest : SysuiTestCase() { .setNotification( Notification.Builder(mContext, "test") .build()) + .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT)) .setUser(mContext.getUser()) .setOverrideGroupKey("") .build() @@ -116,6 +119,7 @@ class NotificationRankingManagerTest : SysuiTestCase() { .setOpPkg("pkg") .setTag("tag") .setNotification(aN) + .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT)) .setUser(mContext.getUser()) .setOverrideGroupKey("") .build() @@ -130,6 +134,7 @@ class NotificationRankingManagerTest : SysuiTestCase() { .setOpPkg("pkg2") .setTag("tag") .setNotification(bN) + .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT)) .setUser(mContext.getUser()) .setOverrideGroupKey("") .build() @@ -149,6 +154,7 @@ class NotificationRankingManagerTest : SysuiTestCase() { .setTag("tag") .setNotification(notif) .setUser(mContext.user) + .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT)) .setOverrideGroupKey("") .build() @@ -168,6 +174,7 @@ class NotificationRankingManagerTest : SysuiTestCase() { .setTag("tag") .setNotification(notif) .setUser(mContext.user) + .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT)) .setOverrideGroupKey("") .build() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java index 3e4068b6367c..e915be37705b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java @@ -16,9 +16,10 @@ package com.android.systemui.statusbar.notification.collection; -import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpList; +import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpTree; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyList; @@ -27,6 +28,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -37,15 +39,17 @@ import android.util.ArrayMap; import androidx.test.filters.SmallTest; +import com.android.systemui.DumpController; import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl.OnRenderListListener; +import com.android.systemui.statusbar.notification.collection.ShadeListBuilder.OnRenderListListener; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.SectionsProvider; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection; +import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener; import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.util.Assert; import com.android.systemui.util.time.FakeSystemClock; @@ -72,9 +76,9 @@ import java.util.stream.Collectors; @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper -public class NotifListBuilderImplTest extends SysuiTestCase { +public class ShadeListBuilderTest extends SysuiTestCase { - private NotifListBuilderImpl mListBuilder; + private ShadeListBuilder mListBuilder; private FakeSystemClock mSystemClock = new FakeSystemClock(); @Mock private NotifLog mNotifLog; @@ -99,7 +103,7 @@ public class NotifListBuilderImplTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); Assert.sMainLooper = TestableLooper.get(this).getLooper(); - mListBuilder = new NotifListBuilderImpl(mSystemClock, mNotifLog); + mListBuilder = new ShadeListBuilder(mSystemClock, mNotifLog, mock(DumpController.class)); mListBuilder.setOnRenderListListener(mOnRenderListListener); mListBuilder.attach(mNotifCollection); @@ -447,6 +451,29 @@ public class NotifListBuilderImplTest extends SysuiTestCase { } @Test + public void testPreRenderNotifsFilteredBreakupGroups() { + final String filterTag = "FILTER_ME"; + // GIVEN a NotifFilter that filters out notifications with a tag + NotifFilter filter1 = spy(new NotifFilterWithTag(filterTag)); + mListBuilder.addPreRenderFilter(filter1); + + // WHEN the pipeline is kicked off on a list of notifs + addGroupChildWithTag(0, PACKAGE_2, GROUP_1, filterTag); + addGroupChild(1, PACKAGE_2, GROUP_1); + addGroupSummary(2, PACKAGE_2, GROUP_1); + dispatchBuild(); + + // THEN the final list doesn't contain any filtered-out notifs + // and groups that are too small are broken up + verifyBuiltList( + notif(1) + ); + + // THEN each filtered notif records the filter that did it + assertEquals(filter1, mEntrySet.get(0).mExcludingFilter); + } + + @Test public void testNotifFiltersCanBePreempted() { // GIVEN two notif filters NotifFilter filter1 = spy(new PackageFilter(PACKAGE_2)); @@ -549,12 +576,17 @@ public class NotifListBuilderImplTest extends SysuiTestCase { } @Test - public void testNotifsAreSectioned() { - // GIVEN a filter that removes all PACKAGE_4 notifs and a SectionsProvider that divides + public void testNotifSections() { + // GIVEN a filter that removes all PACKAGE_4 notifs and sections that divide // notifs based on package name mListBuilder.addPreGroupFilter(new PackageFilter(PACKAGE_4)); - final SectionsProvider sectionsProvider = spy(new PackageSectioner()); - mListBuilder.setSectionsProvider(sectionsProvider); + final NotifSection pkg1Section = spy(new PackageSection(PACKAGE_1)); + final NotifSection pkg2Section = spy(new PackageSection(PACKAGE_2)); + // NOTE: no package 3 section explicitly added, so notifs with package 3 will get set by + // ShadeListBuilder's sDefaultSection which will demote it to the last section + final NotifSection pkg4Section = spy(new PackageSection(PACKAGE_4)); + final NotifSection pkg5Section = spy(new PackageSection(PACKAGE_5)); + mListBuilder.setSections(Arrays.asList(pkg1Section, pkg2Section, pkg4Section, pkg5Section)); // WHEN we build a list with different packages addNotif(0, PACKAGE_4); @@ -581,19 +613,93 @@ public class NotifListBuilderImplTest extends SysuiTestCase { child(6) ), notif(8), - notif(3), - notif(9) + notif(9), + notif(3) + ); + + // THEN the first section (pkg1Section) is called on all top level elements (but + // no children and no entries that were filtered out) + verify(pkg1Section).isInSection(mEntrySet.get(1)); + verify(pkg1Section).isInSection(mEntrySet.get(2)); + verify(pkg1Section).isInSection(mEntrySet.get(3)); + verify(pkg1Section).isInSection(mEntrySet.get(7)); + verify(pkg1Section).isInSection(mEntrySet.get(8)); + verify(pkg1Section).isInSection(mEntrySet.get(9)); + verify(pkg1Section).isInSection(mBuiltList.get(3)); + + verify(pkg1Section, never()).isInSection(mEntrySet.get(0)); + verify(pkg1Section, never()).isInSection(mEntrySet.get(4)); + verify(pkg1Section, never()).isInSection(mEntrySet.get(5)); + verify(pkg1Section, never()).isInSection(mEntrySet.get(6)); + verify(pkg1Section, never()).isInSection(mEntrySet.get(10)); + + // THEN the last section (pkg5Section) is not called on any of the entries that were + // filtered or already in a section + verify(pkg5Section, never()).isInSection(mEntrySet.get(0)); + verify(pkg5Section, never()).isInSection(mEntrySet.get(1)); + verify(pkg5Section, never()).isInSection(mEntrySet.get(2)); + verify(pkg5Section, never()).isInSection(mEntrySet.get(4)); + verify(pkg5Section, never()).isInSection(mEntrySet.get(5)); + verify(pkg5Section, never()).isInSection(mEntrySet.get(6)); + verify(pkg5Section, never()).isInSection(mEntrySet.get(7)); + verify(pkg5Section, never()).isInSection(mEntrySet.get(8)); + verify(pkg5Section, never()).isInSection(mEntrySet.get(10)); + + verify(pkg5Section).isInSection(mEntrySet.get(3)); + verify(pkg5Section).isInSection(mEntrySet.get(9)); + + // THEN the correct section is assigned for entries in pkg1Section + assertEquals(pkg1Section, mEntrySet.get(2).mNotifSection); + assertEquals(0, mEntrySet.get(2).getSection()); + assertEquals(pkg1Section, mEntrySet.get(7).mNotifSection); + assertEquals(0, mEntrySet.get(7).getSection()); + + // THEN the correct section is assigned for entries in pkg2Section + assertEquals(pkg2Section, mEntrySet.get(1).mNotifSection); + assertEquals(1, mEntrySet.get(1).getSection()); + assertEquals(pkg2Section, mEntrySet.get(8).mNotifSection); + assertEquals(1, mEntrySet.get(8).getSection()); + assertEquals(pkg2Section, mBuiltList.get(3).mNotifSection); + assertEquals(1, mBuiltList.get(3).getSection()); + + // THEN no section was assigned to entries in pkg4Section (since they were filtered) + assertEquals(null, mEntrySet.get(0).mNotifSection); + assertEquals(-1, mEntrySet.get(0).getSection()); + assertEquals(null, mEntrySet.get(10).mNotifSection); + assertEquals(-1, mEntrySet.get(10).getSection()); + + + // THEN the correct section is assigned for entries in pkg5Section + assertEquals(pkg5Section, mEntrySet.get(9).mNotifSection); + assertEquals(3, mEntrySet.get(9).getSection()); + + // THEN the children entries are assigned the same section as its parent + assertEquals(mBuiltList.get(3).mNotifSection, child(5).entry.mNotifSection); + assertEquals(mBuiltList.get(3).getSection(), child(5).entry.getSection()); + assertEquals(mBuiltList.get(3).mNotifSection, child(6).entry.mNotifSection); + assertEquals(mBuiltList.get(3).getSection(), child(6).entry.getSection()); + } + + @Test + public void testNotifUsesDefaultSection() { + // GIVEN a Section for Package2 + final NotifSection pkg2Section = spy(new PackageSection(PACKAGE_2)); + mListBuilder.setSections(Arrays.asList(pkg2Section)); + + // WHEN we build a list with pkg1 and pkg2 packages + addNotif(0, PACKAGE_1); + addNotif(1, PACKAGE_2); + dispatchBuild(); + + // THEN the list is sorted according to section + verifyBuiltList( + notif(1), + notif(0) ); - // THEN the sections provider is called on all top level elements (but no children and no - // entries that were filtered out) - verify(sectionsProvider).getSection(mEntrySet.get(1)); - verify(sectionsProvider).getSection(mEntrySet.get(2)); - verify(sectionsProvider).getSection(mEntrySet.get(3)); - verify(sectionsProvider).getSection(mEntrySet.get(7)); - verify(sectionsProvider).getSection(mEntrySet.get(8)); - verify(sectionsProvider).getSection(mEntrySet.get(9)); - verify(sectionsProvider).getSection(mBuiltList.get(3)); + // THEN the entry that didn't have an explicit section gets assigned the DefaultSection + assertEquals(1, notif(0).entry.getSection()); + assertNotNull(notif(0).entry.mNotifSection); } @Test @@ -628,7 +734,7 @@ public class NotifListBuilderImplTest extends SysuiTestCase { // GIVEN a bunch of registered listeners and pluggables NotifFilter preGroupFilter = spy(new PackageFilter(PACKAGE_1)); NotifPromoter promoter = spy(new IdPromoter(3)); - PackageSectioner sectioner = spy(new PackageSectioner()); + NotifSection section = spy(new PackageSection(PACKAGE_1)); NotifComparator comparator = spy(new HypeComparator(PACKAGE_4)); NotifFilter preRenderFilter = spy(new PackageFilter(PACKAGE_5)); mListBuilder.addPreGroupFilter(preGroupFilter); @@ -636,7 +742,7 @@ public class NotifListBuilderImplTest extends SysuiTestCase { mListBuilder.addPromoter(promoter); mListBuilder.addOnBeforeSortListener(mOnBeforeSortListener); mListBuilder.setComparators(Collections.singletonList(comparator)); - mListBuilder.setSectionsProvider(sectioner); + mListBuilder.setSections(Arrays.asList(section)); mListBuilder.addOnBeforeRenderListListener(mOnBeforeRenderListListener); mListBuilder.addPreRenderFilter(preRenderFilter); @@ -656,7 +762,7 @@ public class NotifListBuilderImplTest extends SysuiTestCase { mOnBeforeTransformGroupsListener, promoter, mOnBeforeSortListener, - sectioner, + section, comparator, preRenderFilter, mOnBeforeRenderListListener, @@ -669,7 +775,7 @@ public class NotifListBuilderImplTest extends SysuiTestCase { inOrder.verify(promoter, atLeastOnce()) .shouldPromoteToTopLevel(any(NotificationEntry.class)); inOrder.verify(mOnBeforeSortListener).onBeforeSort(anyList()); - inOrder.verify(sectioner, atLeastOnce()).getSection(any(ListEntry.class)); + inOrder.verify(section, atLeastOnce()).isInSection(any(ListEntry.class)); inOrder.verify(comparator, atLeastOnce()) .compare(any(ListEntry.class), any(ListEntry.class)); inOrder.verify(preRenderFilter, atLeastOnce()) @@ -683,12 +789,12 @@ public class NotifListBuilderImplTest extends SysuiTestCase { // GIVEN a variety of pluggables NotifFilter packageFilter = new PackageFilter(PACKAGE_1); NotifPromoter idPromoter = new IdPromoter(4); - SectionsProvider sectionsProvider = new PackageSectioner(); + NotifSection section = new PackageSection(PACKAGE_1); NotifComparator hypeComparator = new HypeComparator(PACKAGE_2); mListBuilder.addPreGroupFilter(packageFilter); mListBuilder.addPromoter(idPromoter); - mListBuilder.setSectionsProvider(sectionsProvider); + mListBuilder.setSections(Arrays.asList(section)); mListBuilder.setComparators(Collections.singletonList(hypeComparator)); // GIVEN a set of random notifs @@ -708,7 +814,7 @@ public class NotifListBuilderImplTest extends SysuiTestCase { verify(mOnRenderListListener).onRenderList(anyList()); clearInvocations(mOnRenderListListener); - sectionsProvider.invalidateList(); + section.invalidateList(); verify(mOnRenderListListener).onRenderList(anyList()); clearInvocations(mOnRenderListListener); @@ -981,9 +1087,10 @@ public class NotifListBuilderImplTest extends SysuiTestCase { return builder; } - /** Same behavior as {@link #addNotif(int, String)}. */ - private NotificationEntryBuilder addGroupChild(int index, String packageId, String groupId) { + private NotificationEntryBuilder addGroupChildWithTag(int index, String packageId, + String groupId, String tag) { final NotificationEntryBuilder builder = new NotificationEntryBuilder() + .setTag(tag) .setPkg(packageId) .setId(nextId(packageId)) .setRank(nextRank()); @@ -998,6 +1105,11 @@ public class NotifListBuilderImplTest extends SysuiTestCase { return builder; } + /** Same behavior as {@link #addNotif(int, String)}. */ + private NotificationEntryBuilder addGroupChild(int index, String packageId, String groupId) { + return addGroupChildWithTag(index, packageId, groupId, null); + } + private int nextId(String packageName) { Integer nextId = mNextIdMap.get(packageName); if (nextId == null) { @@ -1080,7 +1192,8 @@ public class NotifListBuilderImplTest extends SysuiTestCase { } } catch (AssertionError err) { throw new AssertionError( - "List under test failed verification:\n" + dumpList(mBuiltList), err); + "List under test failed verification:\n" + dumpTree(mBuiltList, + true, ""), err); } } @@ -1165,6 +1278,21 @@ public class NotifListBuilderImplTest extends SysuiTestCase { } } + /** Filters out notifications with a particular tag */ + private static class NotifFilterWithTag extends NotifFilter { + private final String mTag; + + NotifFilterWithTag(String tag) { + super("NotifFilterWithTag_" + tag); + mTag = tag; + } + + @Override + public boolean shouldFilterOut(NotificationEntry entry, long now) { + return Objects.equals(entry.getSbn().getTag(), mTag); + } + } + /** Promotes notifs with particular IDs */ private static class IdPromoter extends NotifPromoter { private final List<Integer> mIds; @@ -1201,25 +1329,18 @@ public class NotifListBuilderImplTest extends SysuiTestCase { } } - /** Sorts notifs into sections based on their package name */ - private static class PackageSectioner extends SectionsProvider { + /** Represents a section for the passed pkg */ + private static class PackageSection extends NotifSection { + private final String mPackage; - PackageSectioner() { - super("PackageSectioner"); + PackageSection(String pkg) { + super("PackageSection_" + pkg); + mPackage = pkg; } @Override - public int getSection(ListEntry entry) { - switch (entry.getRepresentativeEntry().getSbn().getPackageName()) { - case PACKAGE_1: - return 1; - case PACKAGE_2: - return 2; - case PACKAGE_3: - return 3; - default: - return 4; - } + public boolean isInSection(ListEntry entry) { + return entry.getRepresentativeEntry().getSbn().getPackageName().equals(mPackage); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java index 7ff32401b4be..86c1eb97d186 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 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,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.collection.notifcollection; +package com.android.systemui.statusbar.notification.collection.coalescer; import static com.android.internal.util.Preconditions.checkNotNull; @@ -80,7 +80,8 @@ public class GroupCoalescerTest extends SysuiTestCase { mExecutor, mClock, mLog, - LINGER_DURATION); + MIN_LINGER_DURATION, + MAX_LINGER_DURATION); mCoalescer.setNotificationHandler(mListener); mCoalescer.attach(mListenerService); @@ -96,7 +97,7 @@ public class GroupCoalescerTest extends SysuiTestCase { new NotificationEntryBuilder() .setId(0) .setPkg(TEST_PACKAGE_A)); - mClock.advanceTime(LINGER_DURATION); + mClock.advanceTime(MIN_LINGER_DURATION); // THEN the event is passed through to the handler verify(mListener).onNotificationPosted(notif1.sbn, notif1.rankingMap); @@ -144,12 +145,16 @@ public class GroupCoalescerTest extends SysuiTestCase { .setId(1) .setGroup(mContext, GROUP_1)); + mClock.advanceTime(2); + NotifEvent notif2 = mNoMan.postNotif(new NotificationEntryBuilder() .setPkg(TEST_PACKAGE_A) .setId(2) .setGroup(mContext, GROUP_1) .setGroupSummary(mContext, true)); + mClock.advanceTime(3); + NotifEvent notif3 = mNoMan.postNotif(new NotificationEntryBuilder() .setPkg(TEST_PACKAGE_A) .setId(3) @@ -161,7 +166,7 @@ public class GroupCoalescerTest extends SysuiTestCase { verify(mListener, never()).onNotificationBatchPosted(anyList()); // WHEN enough time passes - mClock.advanceTime(LINGER_DURATION); + mClock.advanceTime(MIN_LINGER_DURATION); // THEN the coalesced notifs are applied. The summary is sorted to the front. verify(mListener).onNotificationBatchPosted(Arrays.asList( @@ -212,7 +217,7 @@ public class GroupCoalescerTest extends SysuiTestCase { // WHEN the time runs out on the remainder of the group clearInvocations(mListener); - mClock.advanceTime(LINGER_DURATION); + mClock.advanceTime(MIN_LINGER_DURATION); // THEN no lingering batch is applied verify(mListener, never()).onNotificationBatchPosted(anyList()); @@ -225,11 +230,13 @@ public class GroupCoalescerTest extends SysuiTestCase { .setPkg(TEST_PACKAGE_A) .setId(1) .setGroup(mContext, GROUP_1)); + mClock.advanceTime(2); NotifEvent notif2a = mNoMan.postNotif(new NotificationEntryBuilder() .setPkg(TEST_PACKAGE_A) .setId(2) .setContentTitle(mContext, "Version 1") .setGroup(mContext, GROUP_1)); + mClock.advanceTime(4); // WHEN one of them gets updated NotifEvent notif2b = mNoMan.postNotif(new NotificationEntryBuilder() @@ -248,7 +255,7 @@ public class GroupCoalescerTest extends SysuiTestCase { any(RankingMap.class)); // THEN second, the update is emitted - mClock.advanceTime(LINGER_DURATION); + mClock.advanceTime(MIN_LINGER_DURATION); verify(mListener).onNotificationBatchPosted(Collections.singletonList( new CoalescedEvent(notif2b.key, 0, notif2b.sbn, notif2b.ranking, null) )); @@ -308,14 +315,61 @@ public class GroupCoalescerTest extends SysuiTestCase { .setId(17)); // THEN they have the new rankings when they are eventually emitted - mClock.advanceTime(LINGER_DURATION); + mClock.advanceTime(MIN_LINGER_DURATION); verify(mListener).onNotificationBatchPosted(Arrays.asList( new CoalescedEvent(notif1.key, 0, notif1.sbn, ranking1b, null), new CoalescedEvent(notif2.key, 1, notif2.sbn, ranking2b, null) )); } - private static final long LINGER_DURATION = 4700; + @Test + public void testMaxLingerDuration() { + // GIVEN five coalesced notifications that have collectively taken 20ms to arrive, 2ms + // longer than the max linger duration + NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder() + .setPkg(TEST_PACKAGE_A) + .setId(1) + .setGroup(mContext, GROUP_1)); + mClock.advanceTime(4); + NotifEvent notif2 = mNoMan.postNotif(new NotificationEntryBuilder() + .setPkg(TEST_PACKAGE_A) + .setId(2) + .setGroup(mContext, GROUP_1)); + mClock.advanceTime(4); + NotifEvent notif3 = mNoMan.postNotif(new NotificationEntryBuilder() + .setPkg(TEST_PACKAGE_A) + .setId(3) + .setGroup(mContext, GROUP_1)); + mClock.advanceTime(4); + NotifEvent notif4 = mNoMan.postNotif(new NotificationEntryBuilder() + .setPkg(TEST_PACKAGE_A) + .setId(4) + .setGroup(mContext, GROUP_1)); + mClock.advanceTime(4); + NotifEvent notif5 = mNoMan.postNotif(new NotificationEntryBuilder() + .setPkg(TEST_PACKAGE_A) + .setId(5) + .setGroup(mContext, GROUP_1)); + mClock.advanceTime(4); + + // WHEN a sixth notification arrives + NotifEvent notif6 = mNoMan.postNotif(new NotificationEntryBuilder() + .setPkg(TEST_PACKAGE_A) + .setId(6) + .setGroup(mContext, GROUP_1)); + + // THEN the first five notifications are emitted in a batch + verify(mListener).onNotificationBatchPosted(Arrays.asList( + new CoalescedEvent(notif1.key, 0, notif1.sbn, notif1.ranking, null), + new CoalescedEvent(notif2.key, 1, notif2.sbn, notif2.ranking, null), + new CoalescedEvent(notif3.key, 2, notif3.sbn, notif3.ranking, null), + new CoalescedEvent(notif4.key, 3, notif4.sbn, notif4.ranking, null), + new CoalescedEvent(notif5.key, 4, notif5.sbn, notif5.ranking, null) + )); + } + + private static final long MIN_LINGER_DURATION = 5; + private static final long MAX_LINGER_DURATION = 18; private static final String TEST_PACKAGE_A = "com.test.package_a"; private static final String TEST_PACKAGE_B = "com.test.package_b"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java index ea6c70a6e142..701cf95736ea 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java @@ -36,7 +36,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.RankingBuilder; -import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; @@ -65,7 +65,7 @@ public class DeviceProvisionedCoordinatorTest extends SysuiTestCase { @Mock private ActivityManagerInternal mActivityMangerInternal; @Mock private IPackageManager mIPackageManager; @Mock private DeviceProvisionedController mDeviceProvisionedController; - @Mock private NotifListBuilderImpl mNotifListBuilder; + @Mock private NotifPipeline mNotifPipeline; private Notification mNotification; private NotificationEntry mEntry; private DeviceProvisionedCoordinator mDeviceProvisionedCoordinator; @@ -84,8 +84,8 @@ public class DeviceProvisionedCoordinatorTest extends SysuiTestCase { .build(); ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class); - mDeviceProvisionedCoordinator.attach(null, mNotifListBuilder); - verify(mNotifListBuilder, times(1)).addPreGroupFilter(filterCaptor.capture()); + mDeviceProvisionedCoordinator.attach(mNotifPipeline); + verify(mNotifPipeline, times(1)).addPreGroupFilter(filterCaptor.capture()); mDeviceProvisionedFilter = filterCaptor.getValue(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java index 01bca0dc1078..6cc8dd908760 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java @@ -36,12 +36,11 @@ import androidx.test.filters.SmallTest; import com.android.systemui.ForegroundServiceController; import com.android.systemui.SysuiTestCase; import com.android.systemui.appops.AppOpsController; -import com.android.systemui.statusbar.notification.collection.NotifCollection; -import com.android.systemui.statusbar.notification.collection.NotifLifetimeExtender; -import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; import org.junit.Before; import org.junit.Test; @@ -59,8 +58,7 @@ public class ForegroundCoordinatorTest extends SysuiTestCase { @Mock private Handler mMainHandler; @Mock private ForegroundServiceController mForegroundServiceController; @Mock private AppOpsController mAppOpsController; - @Mock private NotifListBuilderImpl mNotifListBuilder; - @Mock private NotifCollection mNotifCollection; + @Mock private NotifPipeline mNotifPipeline; private NotificationEntry mEntry; private Notification mNotification; @@ -84,9 +82,9 @@ public class ForegroundCoordinatorTest extends SysuiTestCase { ArgumentCaptor<NotifLifetimeExtender> lifetimeExtenderCaptor = ArgumentCaptor.forClass(NotifLifetimeExtender.class); - mForegroundCoordinator.attach(mNotifCollection, mNotifListBuilder); - verify(mNotifListBuilder, times(1)).addPreGroupFilter(filterCaptor.capture()); - verify(mNotifCollection, times(1)).addNotificationLifetimeExtender( + mForegroundCoordinator.attach(mNotifPipeline); + verify(mNotifPipeline, times(1)).addPreGroupFilter(filterCaptor.capture()); + verify(mNotifPipeline, times(1)).addNotificationLifetimeExtender( lifetimeExtenderCaptor.capture()); mForegroundFilter = filterCaptor.getValue(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java index f921cf969e61..5866d90f62bf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java @@ -41,7 +41,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.notification.collection.GroupEntry; -import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; @@ -68,7 +68,7 @@ public class KeyguardCoordinatorTest extends SysuiTestCase { @Mock private StatusBarStateController mStatusBarStateController; @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Mock private HighPriorityProvider mHighPriorityProvider; - @Mock private NotifListBuilderImpl mNotifListBuilder; + @Mock private NotifPipeline mNotifPipeline; private NotificationEntry mEntry; private KeyguardCoordinator mKeyguardCoordinator; @@ -87,8 +87,8 @@ public class KeyguardCoordinatorTest extends SysuiTestCase { .build(); ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class); - mKeyguardCoordinator.attach(null, mNotifListBuilder); - verify(mNotifListBuilder, times(1)).addPreRenderFilter(filterCaptor.capture()); + mKeyguardCoordinator.attach(mNotifPipeline); + verify(mNotifPipeline, times(1)).addPreRenderFilter(filterCaptor.capture()); mKeyguardFilter = filterCaptor.getValue(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java index d3b16c319692..e84f9cf352ed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java @@ -33,7 +33,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.RankingBuilder; -import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; @@ -50,7 +50,7 @@ import org.mockito.MockitoAnnotations; public class RankingCoordinatorTest extends SysuiTestCase { @Mock private StatusBarStateController mStatusBarStateController; - @Mock private NotifListBuilderImpl mNotifListBuilder; + @Mock private NotifPipeline mNotifPipeline; private NotificationEntry mEntry; private RankingCoordinator mRankingCoordinator; private NotifFilter mRankingFilter; @@ -62,8 +62,8 @@ public class RankingCoordinatorTest extends SysuiTestCase { mEntry = new NotificationEntryBuilder().build(); ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class); - mRankingCoordinator.attach(null, mNotifListBuilder); - verify(mNotifListBuilder, times(1)).addPreGroupFilter(filterCaptor.capture()); + mRankingCoordinator.attach(mNotifPipeline); + verify(mNotifPipeline, times(1)).addPreGroupFilter(filterCaptor.capture()); mRankingFilter = filterCaptor.getValue(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java new file mode 100644 index 000000000000..61f0b265e874 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java @@ -0,0 +1,845 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.notification.row; + +import static android.app.Notification.FLAG_BUBBLE; +import static android.app.NotificationManager.IMPORTANCE_DEFAULT; +import static android.app.NotificationManager.IMPORTANCE_HIGH; +import static android.app.NotificationManager.IMPORTANCE_LOW; +import static android.print.PrintManager.PRINT_SPOOLER_PACKAGE_NAME; +import static android.view.View.GONE; +import static android.view.View.VISIBLE; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.INotificationManager; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationChannelGroup; +import android.app.PendingIntent; +import android.app.Person; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.LauncherApps; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; +import android.os.UserHandle; +import android.service.notification.StatusBarNotification; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.util.Slog; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.Button; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.TextView; + +import com.android.internal.logging.MetricsLogger; +import com.android.systemui.Dependency; +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.bubbles.BubbleController; +import com.android.systemui.bubbles.BubblesTestActivity; +import com.android.systemui.statusbar.SbnBuilder; +import com.android.systemui.statusbar.notification.VisualStabilityManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.mockito.stubbing.Answer; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CountDownLatch; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class NotificationConversationInfoTest extends SysuiTestCase { + private static final String TEST_PACKAGE_NAME = "test_package"; + private static final String TEST_SYSTEM_PACKAGE_NAME = PRINT_SPOOLER_PACKAGE_NAME; + private static final int TEST_UID = 1; + private static final String TEST_CHANNEL = "test_channel"; + private static final String TEST_CHANNEL_NAME = "TEST CHANNEL NAME"; + private static final String CONVERSATION_ID = "convo"; + + private TestableLooper mTestableLooper; + private NotificationConversationInfo mNotificationInfo; + private NotificationChannel mNotificationChannel; + private NotificationChannel mConversationChannel; + private StatusBarNotification mSbn; + private NotificationEntry mEntry; + private StatusBarNotification mBubbleSbn; + private NotificationEntry mBubbleEntry; + @Mock + private ShortcutInfo mShortcutInfo; + private Drawable mImage; + + @Rule + public MockitoRule mockito = MockitoJUnit.rule(); + @Mock + private MetricsLogger mMetricsLogger; + @Mock + private INotificationManager mMockINotificationManager; + @Mock + private PackageManager mMockPackageManager; + @Mock + private VisualStabilityManager mVisualStabilityManager; + @Mock + private BubbleController mBubbleController; + @Mock + private LauncherApps mLauncherApps; + @Mock + private ShortcutManager mShortcutManager; + @Mock + private NotificationGuts mNotificationGuts; + + @Before + public void setUp() throws Exception { + mTestableLooper = TestableLooper.get(this); + + mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper()); + mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger); + mDependency.injectTestDependency(BubbleController.class, mBubbleController); + // Inflate the layout + final LayoutInflater layoutInflater = LayoutInflater.from(mContext); + mNotificationInfo = (NotificationConversationInfo) layoutInflater.inflate( + R.layout.notification_conversation_info, + null); + mNotificationInfo.mShowHomeScreen = true; + mNotificationInfo.setGutsParent(mNotificationGuts); + doAnswer((Answer<Object>) invocation -> { + mNotificationInfo.handleCloseControls(true, false); + return null; + }).when(mNotificationGuts).closeControls(anyInt(), anyInt(), eq(true), eq(false)); + // Our view is never attached to a window so the View#post methods in NotificationInfo never + // get called. Setting this will skip the post and do the action immediately. + mNotificationInfo.mSkipPost = true; + + // PackageManager must return a packageInfo and applicationInfo. + final PackageInfo packageInfo = new PackageInfo(); + packageInfo.packageName = TEST_PACKAGE_NAME; + when(mMockPackageManager.getPackageInfo(eq(TEST_PACKAGE_NAME), anyInt())) + .thenReturn(packageInfo); + final ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.uid = TEST_UID; // non-zero + when(mMockPackageManager.getApplicationInfo(eq(TEST_PACKAGE_NAME), anyInt())).thenReturn( + applicationInfo); + final PackageInfo systemPackageInfo = new PackageInfo(); + systemPackageInfo.packageName = TEST_SYSTEM_PACKAGE_NAME; + when(mMockPackageManager.getPackageInfo(eq(TEST_SYSTEM_PACKAGE_NAME), anyInt())) + .thenReturn(systemPackageInfo); + when(mMockPackageManager.getPackageInfo(eq("android"), anyInt())) + .thenReturn(packageInfo); + + when(mShortcutInfo.getShortLabel()).thenReturn("Convo name"); + List<ShortcutInfo> shortcuts = Arrays.asList(mShortcutInfo); + when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcuts); + mImage = mContext.getDrawable(R.drawable.ic_star); + when(mLauncherApps.getShortcutBadgedIconDrawable(eq(mShortcutInfo), + anyInt())).thenReturn(mImage); + + mNotificationChannel = new NotificationChannel( + TEST_CHANNEL, TEST_CHANNEL_NAME, IMPORTANCE_LOW); + + Notification notification = new Notification.Builder(mContext, mNotificationChannel.getId()) + .setShortcutId(CONVERSATION_ID) + .setStyle(new Notification.MessagingStyle(new Person.Builder().setName("m").build()) + .addMessage(new Notification.MessagingStyle.Message( + "hello!", 1000, new Person.Builder().setName("other").build()))) + .build(); + mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0, + notification, UserHandle.CURRENT, null, 0); + mEntry = new NotificationEntryBuilder().setSbn(mSbn).build(); + + PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, + new Intent(mContext, BubblesTestActivity.class), 0); + mBubbleSbn = new SbnBuilder(mSbn).setBubbleMetadata( + new Notification.BubbleMetadata.Builder() + .setIntent(bubbleIntent) + .setIcon(Icon.createWithResource(mContext, R.drawable.android)).build()) + .build(); + mBubbleEntry = new NotificationEntryBuilder().setSbn(mBubbleSbn).build(); + + mConversationChannel = new NotificationChannel( + TEST_CHANNEL + " : " + CONVERSATION_ID, TEST_CHANNEL_NAME, IMPORTANCE_LOW); + mConversationChannel.setConversationId(TEST_CHANNEL, CONVERSATION_ID); + when(mMockINotificationManager.getConversationNotificationChannel(anyString(), anyInt(), + anyString(), eq(TEST_CHANNEL), eq(false), eq(CONVERSATION_ID))) + .thenReturn(mConversationChannel); + } + + @Test + public void testBindNotification_SetsTextShortcutName() { + mNotificationInfo.bindNotification( + mShortcutManager, + mLauncherApps, + mMockPackageManager, + mMockINotificationManager, + mVisualStabilityManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mEntry, + null, + null, + null, + true); + final TextView textView = mNotificationInfo.findViewById(R.id.name); + assertEquals(mShortcutInfo.getShortLabel(), textView.getText().toString()); + assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility()); + } + + @Test + public void testBindNotification_SetsShortcutIcon() { + mNotificationInfo.bindNotification( + mShortcutManager, + mLauncherApps, + mMockPackageManager, + mMockINotificationManager, + mVisualStabilityManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mEntry, + null, + null, + null, + true); + final ImageView view = mNotificationInfo.findViewById(R.id.conversation_icon); + assertEquals(mImage, view.getDrawable()); + } + + @Test + public void testBindNotification_SetsTextApplicationName() { + when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name"); + mNotificationInfo.bindNotification( + mShortcutManager, + mLauncherApps, + mMockPackageManager, + mMockINotificationManager, + mVisualStabilityManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mEntry, + null, + null, + null, + true); + final TextView textView = mNotificationInfo.findViewById(R.id.pkg_name); + assertTrue(textView.getText().toString().contains("App Name")); + assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility()); + } + + @Test + public void testBindNotification_SetsTextChannelName() { + mNotificationInfo.bindNotification( + mShortcutManager, + mLauncherApps, + mMockPackageManager, + mMockINotificationManager, + mVisualStabilityManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mEntry, + null, + null, + null, + true); + final TextView textView = mNotificationInfo.findViewById(R.id.parent_channel_name); + assertTrue(textView.getText().toString().contains(mNotificationChannel.getName())); + assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility()); + } + + @Test + public void testBindNotification_SetsTextGroupName() throws Exception { + NotificationChannelGroup group = new NotificationChannelGroup("id", "name"); + when(mMockINotificationManager.getNotificationChannelGroupForPackage( + anyString(), anyString(), anyInt())).thenReturn(group); + mNotificationChannel.setGroup(group.getId()); + mConversationChannel.setGroup(group.getId()); + + mNotificationInfo.bindNotification( + mShortcutManager, + mLauncherApps, + mMockPackageManager, + mMockINotificationManager, + mVisualStabilityManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mEntry, + null, + null, + null, + true); + final TextView textView = mNotificationInfo.findViewById(R.id.group_name); + assertTrue(textView.getText().toString().contains(group.getName())); + assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility()); + assertEquals(VISIBLE, textView.getVisibility()); + assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.group_divider).getVisibility()); + } + + @Test + public void testBindNotification_GroupNameHiddenIfNoGroup() { + mNotificationInfo.bindNotification( + mShortcutManager, + mLauncherApps, + mMockPackageManager, + mMockINotificationManager, + mVisualStabilityManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mEntry, + null, + null, + null, + true); + final TextView textView = mNotificationInfo.findViewById(R.id.group_name); + assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility()); + assertEquals(GONE, textView.getVisibility()); + assertEquals(GONE, mNotificationInfo.findViewById(R.id.group_divider).getVisibility()); + } + + @Test + public void testBindNotification_noDelegate() { + mNotificationInfo.bindNotification( + mShortcutManager, + mLauncherApps, + mMockPackageManager, + mMockINotificationManager, + mVisualStabilityManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mEntry, + null, + null, + null, + true); + final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name); + assertEquals(GONE, nameView.getVisibility()); + final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider); + assertEquals(GONE, dividerView.getVisibility()); + } + + @Test + public void testBindNotification_delegate() throws Exception { + mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, "other", 0, null, TEST_UID, 0, + mSbn.getNotification(), UserHandle.CURRENT, null, 0); + final ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.uid = 7; // non-zero + when(mMockPackageManager.getApplicationInfo(eq("other"), anyInt())).thenReturn( + applicationInfo); + when(mMockPackageManager.getApplicationLabel(any())).thenReturn("Other"); + + NotificationEntry entry = new NotificationEntryBuilder().setSbn(mSbn).build(); + mNotificationInfo.bindNotification( + mShortcutManager, + mLauncherApps, + mMockPackageManager, + mMockINotificationManager, + mVisualStabilityManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + entry, + null, + null, + null, + true); + final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name); + assertEquals(VISIBLE, nameView.getVisibility()); + assertTrue(nameView.getText().toString().contains("Proxied")); + final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider); + assertEquals(VISIBLE, dividerView.getVisibility()); + } + + @Test + public void testBindNotification_SetsOnClickListenerForSettings() { + final CountDownLatch latch = new CountDownLatch(1); + mNotificationInfo.bindNotification( + mShortcutManager, + mLauncherApps, + mMockPackageManager, + mMockINotificationManager, + mVisualStabilityManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mEntry, + (View v, NotificationChannel c, int appUid) -> { + assertEquals(mConversationChannel, c); + latch.countDown(); + }, + null, + null, + true); + + final View settingsButton = mNotificationInfo.findViewById(R.id.info); + settingsButton.performClick(); + // Verify that listener was triggered. + assertEquals(0, latch.getCount()); + } + + @Test + public void testBindNotification_SettingsButtonInvisibleWhenNoClickListener() { + mNotificationInfo.bindNotification( + mShortcutManager, + mLauncherApps, + mMockPackageManager, + mMockINotificationManager, + mVisualStabilityManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mEntry, + null, + null, + null, + true); + final View settingsButton = mNotificationInfo.findViewById(R.id.info); + assertTrue(settingsButton.getVisibility() != View.VISIBLE); + } + + @Test + public void testBindNotification_SettingsButtonInvisibleWhenDeviceUnprovisioned() { + final CountDownLatch latch = new CountDownLatch(1); + mNotificationInfo.bindNotification( + mShortcutManager, + mLauncherApps, + mMockPackageManager, + mMockINotificationManager, + mVisualStabilityManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mEntry, + (View v, NotificationChannel c, int appUid) -> { + assertEquals(mNotificationChannel, c); + latch.countDown(); + }, + null, + null, + false); + final View settingsButton = mNotificationInfo.findViewById(R.id.info); + assertTrue(settingsButton.getVisibility() != View.VISIBLE); + } + + @Test + public void testBindNotification_bubbleActionVisibleWhenCanBubble() { + mNotificationInfo.bindNotification( + mShortcutManager, + mLauncherApps, + mMockPackageManager, + mMockINotificationManager, + mVisualStabilityManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mBubbleEntry, + null, + null, + null, + true); + + View bubbleView = mNotificationInfo.findViewById(R.id.bubble); + assertEquals(View.VISIBLE, bubbleView.getVisibility()); + } + + @Test + public void testBindNotification_bubbleActionVisibleWhenCannotBubble() { + mNotificationInfo.bindNotification( + mShortcutManager, + mLauncherApps, + mMockPackageManager, + mMockINotificationManager, + mVisualStabilityManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mEntry, + null, + null, + null, + true); + + View bubbleView = mNotificationInfo.findViewById(R.id.bubble); + assertEquals(View.GONE, bubbleView.getVisibility()); + } + + @Test + public void testAddToHome() throws Exception { + when(mShortcutManager.isRequestPinShortcutSupported()).thenReturn(true); + + mNotificationInfo.bindNotification( + mShortcutManager, + mLauncherApps, + mMockPackageManager, + mMockINotificationManager, + mVisualStabilityManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mBubbleEntry, + null, + null, + null, + true); + + + // Promote it + mNotificationInfo.findViewById(R.id.home).performClick(); + mTestableLooper.processAllMessages(); + + verify(mShortcutManager, times(1)).requestPinShortcut(mShortcutInfo, null); + verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( + anyString(), anyInt(), any()); + } + + @Test + public void testSnooze() throws Exception { + final CountDownLatch latch = new CountDownLatch(1); + + mNotificationInfo.bindNotification( + mShortcutManager, + mLauncherApps, + mMockPackageManager, + mMockINotificationManager, + mVisualStabilityManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mBubbleEntry, + null, + null, + (View v, int hours) -> { + latch.countDown(); + }, + true); + + + // Promote it + mNotificationInfo.findViewById(R.id.snooze).performClick(); + mTestableLooper.processAllMessages(); + + assertEquals(0, latch.getCount()); + verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( + anyString(), anyInt(), any()); + } + + @Test + public void testBubble_promotesBubble() throws Exception { + mNotificationChannel.setAllowBubbles(false); + mConversationChannel.setAllowBubbles(false); + + mNotificationInfo.bindNotification( + mShortcutManager, + mLauncherApps, + mMockPackageManager, + mMockINotificationManager, + mVisualStabilityManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mBubbleEntry, + null, + null, + null, + true); + + assertFalse(mBubbleEntry.isBubble()); + + // Promote it + mNotificationInfo.findViewById(R.id.bubble).performClick(); + mTestableLooper.processAllMessages(); + + verify(mBubbleController, times(1)).onUserCreatedBubbleFromNotification(mBubbleEntry); + ArgumentCaptor<NotificationChannel> captor = + ArgumentCaptor.forClass(NotificationChannel.class); + verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( + anyString(), anyInt(), captor.capture()); + assertTrue(captor.getValue().canBubble()); + } + + @Test + public void testBubble_demotesBubble() throws Exception { + mBubbleEntry.getSbn().getNotification().flags |= FLAG_BUBBLE; + + mNotificationInfo.bindNotification( + mShortcutManager, + mLauncherApps, + mMockPackageManager, + mMockINotificationManager, + mVisualStabilityManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mBubbleEntry, + null, + null, + null, + true); + + assertTrue(mBubbleEntry.isBubble()); + + // Demote it + mNotificationInfo.findViewById(R.id.bubble).performClick(); + mTestableLooper.processAllMessages(); + + verify(mBubbleController, times(1)).onUserDemotedBubbleFromNotification(mBubbleEntry); + ArgumentCaptor<NotificationChannel> captor = + ArgumentCaptor.forClass(NotificationChannel.class); + verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( + anyString(), anyInt(), captor.capture()); + assertFalse(captor.getValue().canBubble()); + } + + @Test + public void testFavorite_favorite() throws Exception { + mNotificationInfo.bindNotification( + mShortcutManager, + mLauncherApps, + mMockPackageManager, + mMockINotificationManager, + mVisualStabilityManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mEntry, + null, + null, + null, + true); + + + Button fave = mNotificationInfo.findViewById(R.id.fave); + assertEquals(mContext.getString(R.string.notification_conversation_favorite), + fave.getText().toString()); + + fave.performClick(); + mTestableLooper.processAllMessages(); + + ArgumentCaptor<NotificationChannel> captor = + ArgumentCaptor.forClass(NotificationChannel.class); + verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( + anyString(), anyInt(), captor.capture()); + assertTrue(captor.getValue().canBypassDnd()); + } + + @Test + public void testFavorite_unfavorite() throws Exception { + mNotificationChannel.setBypassDnd(true); + mConversationChannel.setBypassDnd(true); + + mNotificationInfo.bindNotification( + mShortcutManager, + mLauncherApps, + mMockPackageManager, + mMockINotificationManager, + mVisualStabilityManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mEntry, + null, + null, + null, + true); + + Button fave = mNotificationInfo.findViewById(R.id.fave); + assertEquals(mContext.getString(R.string.notification_conversation_unfavorite), + fave.getText().toString()); + + fave.performClick(); + mTestableLooper.processAllMessages(); + + ArgumentCaptor<NotificationChannel> captor = + ArgumentCaptor.forClass(NotificationChannel.class); + verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( + anyString(), anyInt(), captor.capture()); + assertFalse(captor.getValue().canBypassDnd()); + } + + @Test + public void testDemote() throws Exception { + mNotificationInfo.bindNotification( + mShortcutManager, + mLauncherApps, + mMockPackageManager, + mMockINotificationManager, + mVisualStabilityManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mEntry, + null, + null, + null, + true); + + + ImageButton demote = mNotificationInfo.findViewById(R.id.demote); + demote.performClick(); + mTestableLooper.processAllMessages(); + + ArgumentCaptor<NotificationChannel> captor = + ArgumentCaptor.forClass(NotificationChannel.class); + verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( + anyString(), anyInt(), captor.capture()); + assertTrue(captor.getValue().isDemoted()); + } + + @Test + public void testMute_mute() throws Exception { + mNotificationChannel.setImportance(IMPORTANCE_DEFAULT); + mConversationChannel.setImportance(IMPORTANCE_DEFAULT); + + mNotificationInfo.bindNotification( + mShortcutManager, + mLauncherApps, + mMockPackageManager, + mMockINotificationManager, + mVisualStabilityManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mEntry, + null, + null, + null, + true); + + Button mute = mNotificationInfo.findViewById(R.id.mute); + assertEquals(mContext.getString(R.string.notification_conversation_mute), + mute.getText().toString()); + + mute.performClick(); + mTestableLooper.processAllMessages(); + + ArgumentCaptor<NotificationChannel> captor = + ArgumentCaptor.forClass(NotificationChannel.class); + verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( + anyString(), anyInt(), captor.capture()); + assertEquals(IMPORTANCE_LOW, captor.getValue().getImportance()); + } + + @Test + public void testMute_unmute() throws Exception { + mNotificationChannel.setImportance(IMPORTANCE_LOW); + mNotificationChannel.setOriginalImportance(IMPORTANCE_HIGH); + mConversationChannel.setImportance(IMPORTANCE_LOW); + mConversationChannel.setOriginalImportance(IMPORTANCE_HIGH); + + mNotificationInfo.bindNotification( + mShortcutManager, + mLauncherApps, + mMockPackageManager, + mMockINotificationManager, + mVisualStabilityManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mEntry, + null, + null, + null, + true); + + + Button mute = mNotificationInfo.findViewById(R.id.mute); + assertEquals(mContext.getString(R.string.notification_conversation_unmute), + mute.getText().toString()); + + mute.performClick(); + mTestableLooper.processAllMessages(); + + ArgumentCaptor<NotificationChannel> captor = + ArgumentCaptor.forClass(NotificationChannel.class); + verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( + anyString(), anyInt(), captor.capture()); + assertEquals(IMPORTANCE_HIGH, captor.getValue().getImportance()); + } + + @Test + public void testBindNotification_createsNewChannel() throws Exception { + mNotificationInfo.bindNotification( + mShortcutManager, + mLauncherApps, + mMockPackageManager, + mMockINotificationManager, + mVisualStabilityManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mEntry, + null, + null, + null, + true); + verify(mMockINotificationManager, times(1)).createConversationNotificationChannelForPackage( + anyString(), anyInt(), any(), eq(CONVERSATION_ID)); + } + + @Test + public void testBindNotification_doesNotCreateNewChannelIfExists() throws Exception { + mNotificationChannel.setConversationId("", CONVERSATION_ID); + mNotificationInfo.bindNotification( + mShortcutManager, + mLauncherApps, + mMockPackageManager, + mMockINotificationManager, + mVisualStabilityManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mEntry, + null, + null, + null, + true); + verify(mMockINotificationManager, never()).createConversationNotificationChannelForPackage( + anyString(), anyInt(), any(), eq(CONVERSATION_ID)); + } + + @Test + public void testAdjustImportanceTemporarilyAllowsReordering() { + mNotificationChannel.setImportance(IMPORTANCE_DEFAULT); + mConversationChannel.setImportance(IMPORTANCE_DEFAULT); + mNotificationInfo.bindNotification( + mShortcutManager, + mLauncherApps, + mMockPackageManager, + mMockINotificationManager, + mVisualStabilityManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mEntry, + null, + null, + null, + true); + + mNotificationInfo.findViewById(R.id.mute).performClick(); + + verify(mVisualStabilityManager).temporarilyAllowReordering(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java index f48c40c3b941..b33d26f3f07b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java @@ -132,16 +132,6 @@ public class NotificationMenuRowTest extends LeakCheckedTest { } @Test - public void testNoAppOpsInSlowSwipe_biDirectionalSwipe() { - NotificationMenuRow row = new NotificationMenuRow(mContext, true); - row.createMenu(mRow, null); - - ViewGroup container = (ViewGroup) row.getMenuView(); - // in the new interruption model there is only the blocking item - assertEquals(1, container.getChildCount()); - } - - @Test public void testIsSnappedAndOnSameSide() { NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext))); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 9bd3914da3da..d9939f485ce5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -71,7 +71,7 @@ import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; -import com.android.systemui.statusbar.notification.collection.NotificationRowBinder; +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.statusbar.notification.people.PeopleHubSectionFooterViewAdapter; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java index 5ac7bfbfd296..782e14c83951 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java @@ -54,7 +54,7 @@ import com.android.systemui.statusbar.notification.NotificationInterruptionState import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; -import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl; +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; 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 560aadb93a14..fee48522683d 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 @@ -120,8 +120,8 @@ import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; -import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl; -import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline; +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; +import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; @@ -214,7 +214,7 @@ public class StatusBarTest extends SysuiTestCase { @Mock private NotificationWakeUpCoordinator mNotificationWakeUpCoordinator; @Mock private KeyguardBypassController mKeyguardBypassController; @Mock private DynamicPrivacyController mDynamicPrivacyController; - @Mock private NewNotifPipeline mNewNotifPipeline; + @Mock private NotifPipelineInitializer mNewNotifPipeline; @Mock private ZenModeController mZenModeController; @Mock private AutoHideController mAutoHideController; @Mock private NotificationViewHierarchyManager mNotificationViewHierarchyManager; 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 9cb06a5d85c1..13f3a5fbfff7 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 @@ -16,6 +16,9 @@ package com.android.systemui.statusbar.policy; +import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN; +import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; @@ -39,6 +42,7 @@ import android.net.wifi.WifiManager; import android.os.Handler; import android.provider.Settings; import android.provider.Settings.Global; +import android.telephony.NetworkRegistrationInfo; import android.telephony.PhoneStateListener; import android.telephony.ServiceState; import android.telephony.SignalStrength; @@ -358,7 +362,13 @@ public class NetworkControllerBaseTest extends SysuiTestCase { } public void updateDataConnectionState(int dataState, int dataNetType) { - when(mServiceState.getDataNetworkType()).thenReturn(dataNetType); + NetworkRegistrationInfo fakeRegInfo = new NetworkRegistrationInfo.Builder() + .setTransportType(TRANSPORT_TYPE_WWAN) + .setDomain(DOMAIN_PS) + .setAccessNetworkTechnology(dataNetType) + .build(); + when(mServiceState.getNetworkRegistrationInfo(DOMAIN_PS, TRANSPORT_TYPE_WWAN)) + .thenReturn(fakeRegInfo); mPhoneStateListener.onDataConnectionStateChanged(dataState, dataNetType); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java index 5a5ef8bd21af..f6c750db56b7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java @@ -1,5 +1,8 @@ package com.android.systemui.statusbar.policy; +import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN; +import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS; + import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.anyInt; @@ -418,7 +421,13 @@ public class NetworkControllerDataTest extends NetworkControllerBaseTest { // State from NR_5G to NONE NR_STATE_RESTRICTED, showing corresponding icon doReturn(NetworkRegistrationInfo.NR_STATE_RESTRICTED).when(mServiceState).getNrState(); - doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType(); + NetworkRegistrationInfo fakeRegInfo = new NetworkRegistrationInfo.Builder() + .setTransportType(TRANSPORT_TYPE_WWAN) + .setDomain(DOMAIN_PS) + .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE) + .build(); + doReturn(fakeRegInfo).when(mServiceState) + .getNetworkRegistrationInfo(DOMAIN_PS, TRANSPORT_TYPE_WWAN); mPhoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED, TelephonyManager.NETWORK_TYPE_LTE); @@ -480,8 +489,13 @@ public class NetworkControllerDataTest extends NetworkControllerBaseTest { verifyDataIndicators(TelephonyIcons.ICON_LTE); - when(mServiceState.getDataNetworkType()) - .thenReturn(TelephonyManager.NETWORK_TYPE_HSPA); + NetworkRegistrationInfo fakeRegInfo = new NetworkRegistrationInfo.Builder() + .setTransportType(TRANSPORT_TYPE_WWAN) + .setDomain(DOMAIN_PS) + .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_HSPA) + .build(); + when(mServiceState.getNetworkRegistrationInfo(DOMAIN_PS, TRANSPORT_TYPE_WWAN)) + .thenReturn(fakeRegInfo); updateServiceState(); verifyDataIndicators(TelephonyIcons.ICON_H); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java index 4103d7189266..cd89d3c32697 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java @@ -30,12 +30,12 @@ import android.telephony.CellSignalStrength; import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; -import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.TelephonyIntents; import com.android.settingslib.graph.SignalDrawable; import com.android.settingslib.net.DataUsageController; @@ -418,7 +418,7 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { intent.putExtra(TelephonyIntents.EXTRA_SHOW_PLMN, showPlmn); intent.putExtra(TelephonyIntents.EXTRA_PLMN, plmn); - intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, mSubId); + SubscriptionManager.putSubscriptionIdExtra(intent, mSubId); return intent; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt index 709a1a813596..a785d4ba2cb8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt @@ -19,6 +19,7 @@ import com.android.systemui.util.animation.PhysicsAnimatorTestUtils.verifyAnimat import org.junit.After import org.junit.Assert import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse import org.junit.Assert.assertNotEquals import org.junit.Assert.assertTrue import org.junit.Before @@ -154,6 +155,7 @@ class PhysicsAnimatorTest : SysuiTestCase() { verify(mockEndListener).onAnimationEnd( testView, DynamicAnimation.TRANSLATION_X, + wasFling = false, canceled = false, finalValue = 10f, finalVelocity = 0f, @@ -175,6 +177,7 @@ class PhysicsAnimatorTest : SysuiTestCase() { verify(mockEndListener).onAnimationEnd( testView, DynamicAnimation.TRANSLATION_Y, + wasFling = false, canceled = false, finalValue = 500f, finalVelocity = 0f, @@ -214,8 +217,8 @@ class PhysicsAnimatorTest : SysuiTestCase() { verifyUpdateListenerCalls(animator, mockUpdateListener) verify(mockEndListener, times(1)).onAnimationEnd( - eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), anyFloat(), anyFloat(), - eq(true)) + eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), eq(false), anyFloat(), + anyFloat(), eq(true)) verify(mockEndAction, times(1)).run() animator @@ -329,13 +332,24 @@ class PhysicsAnimatorTest : SysuiTestCase() { // The original end listener should also have been called, with allEnded = true since it was // provided to an animator that animated only TRANSLATION_X. verify(mockEndListener, times(1)) - .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X, false, 200f, 0f, true) + .onAnimationEnd( + testView, DynamicAnimation.TRANSLATION_X, + wasFling = false, + canceled = false, + finalValue = 200f, + finalVelocity = 0f, + allRelevantPropertyAnimsEnded = true) verifyNoMoreInteractions(mockEndListener) // The second end listener should have been called, but with allEnded = false since it was // provided to an animator that animated both TRANSLATION_X and TRANSLATION_Y. verify(secondEndListener, times(1)) - .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X, false, 200f, 0f, false) + .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X, + wasFling = false, + canceled = false, + finalValue = 200f, + finalVelocity = 0f, + allRelevantPropertyAnimsEnded = false) verifyNoMoreInteractions(secondEndListener) PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(animator, DynamicAnimation.TRANSLATION_Y) @@ -345,7 +359,12 @@ class PhysicsAnimatorTest : SysuiTestCase() { verifyNoMoreInteractions(mockEndListener) verify(secondEndListener, times(1)) - .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_Y, false, 4000f, 0f, true) + .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_Y, + wasFling = false, + canceled = false, + finalValue = 4000f, + finalVelocity = 0f, + allRelevantPropertyAnimsEnded = true) verifyNoMoreInteractions(secondEndListener) } @@ -364,8 +383,8 @@ class PhysicsAnimatorTest : SysuiTestCase() { assertEquals(10f, testView.translationX, 1f) verify(mockEndListener, times(1)) .onAnimationEnd( - eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), eq(10f), - anyFloat(), eq(true)) + eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(true), eq(false), + eq(10f), anyFloat(), eq(true)) animator .fling( @@ -381,8 +400,39 @@ class PhysicsAnimatorTest : SysuiTestCase() { assertEquals(-5f, testView.translationX, 1f) verify(mockEndListener, times(1)) .onAnimationEnd( - eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), eq(-5f), - anyFloat(), eq(true)) + eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(true), eq(false), + eq(-5f), anyFloat(), eq(true)) + } + + @Test + fun testIsPropertyAnimating() { + PhysicsAnimatorTestUtils.setAllAnimationsBlock(false) + + testView.physicsAnimator + .spring(DynamicAnimation.TRANSLATION_X, 500f, springConfig) + .fling(DynamicAnimation.TRANSLATION_Y, 10f, flingConfig) + .spring(DynamicAnimation.TRANSLATION_Z, 1000f, springConfig) + .start() + + // All of the properties we just started should be animating. + assertTrue(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_X)) + assertTrue(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Y)) + assertTrue(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Z)) + + // Block until x and y end. + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(testView.physicsAnimator, + DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y) + + // Verify that x and y are no longer animating, but that Z is (it's springing to 1000f). + assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_X)) + assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Y)) + assertTrue(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Z)) + + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Z) + + assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_X)) + assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Y)) + assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Z)) } @Test @@ -395,6 +445,118 @@ class PhysicsAnimatorTest : SysuiTestCase() { assertEquals(200f, testView.translationX, 1f) } + @Test + fun testFlingThenSpring() { + PhysicsAnimatorTestUtils.setAllAnimationsBlock(false) + + // Start at 500f and fling hard to the left. We should quickly reach the 250f minimum, fly + // past it since there's so much velocity remaining, then spring back to 250f. + testView.translationX = 500f + animator + .flingThenSpring( + DynamicAnimation.TRANSLATION_X, + -5000f, + flingConfig.apply { min = 250f }, + springConfig) + .addUpdateListener(mockUpdateListener) + .addEndListener(mockEndListener) + .withEndActions(mockEndAction::run) + .start() + + // Block until we pass the minimum. + PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue( + animator) { v -> v.translationX <= 250f } + + // Double check that the view is there. + assertTrue(testView.translationX <= 250f) + + // The update listener should have been called with a value < 500f, and then a value less + // than or equal to the 250f minimum. + verifyAnimationUpdateFrames(animator, DynamicAnimation.TRANSLATION_X, + { u -> u.value < 500f }, + { u -> u.value <= 250f }) + + // Despite the fact that the fling has ended, the end listener shouldn't have been called + // since we're about to begin springing the same property. + verifyNoMoreInteractions(mockEndListener) + verifyNoMoreInteractions(mockEndAction) + + // Wait for the spring to finish. + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_X) + + // Make sure we continued past 250f since the spring should have been started with some + // remaining negative velocity from the fling. + verifyAnimationUpdateFrames(animator, DynamicAnimation.TRANSLATION_X, + { u -> u.value < 250f }) + + // At this point, the animation end listener should have been called once, and only once, + // when the spring ended at 250f. + verify(mockEndListener).onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X, + wasFling = false, + canceled = false, + finalValue = 250f, + finalVelocity = 0f, + allRelevantPropertyAnimsEnded = true) + verifyNoMoreInteractions(mockEndListener) + + // The end action should also have been called once. + verify(mockEndAction, times(1)).run() + verifyNoMoreInteractions(mockEndAction) + + assertEquals(250f, testView.translationX) + } + + @Test + fun testFlingThenSpring_objectOutsideFlingBounds() { + // Start the view at x = -500, well outside the fling bounds of min = 0f, with strong + // negative velocity. + testView.translationX = -500f + animator + .flingThenSpring( + DynamicAnimation.TRANSLATION_X, + -5000f, + flingConfig.apply { min = 0f }, + springConfig) + .addUpdateListener(mockUpdateListener) + .addEndListener(mockEndListener) + .withEndActions(mockEndAction::run) + .start() + + // The initial -5000f velocity should result in frames to the left of -500f before the view + // springs back towards 0f. + verifyAnimationUpdateFrames( + animator, DynamicAnimation.TRANSLATION_X, + { u -> u.value < -500f }, + { u -> u.value > -500f }) + + // We should end up at the fling min. + assertEquals(0f, testView.translationX, 1f) + } + + @Test + fun testFlingToMinMaxThenSpring() { + // Start at x = 500f. + testView.translationX = 500f + + // Fling to the left at the very sad rate of -1 pixels per second. That won't get us much of + // anywhere, and certainly not to the 0f min. + animator + // Good thing we have flingToMinMaxThenSpring! + .flingThenSpring( + DynamicAnimation.TRANSLATION_X, + -10000f, + flingConfig.apply { min = 0f }, + springConfig, + flingMustReachMinOrMax = true) + .addUpdateListener(mockUpdateListener) + .addEndListener(mockEndListener) + .withEndActions(mockEndAction::run) + .start() + + // Thanks, flingToMinMaxThenSpring, for adding enough velocity to get us here. + assertEquals(0f, testView.translationX, 1f) + } + /** * Verifies that the calls to the mock update listener match the animation update frames * reported by the test internal listener, in order. diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp index d297f3f84189..e441fb508f79 100644 --- a/packages/Tethering/Android.bp +++ b/packages/Tethering/Android.bp @@ -16,7 +16,7 @@ java_defaults { name: "TetheringAndroidLibraryDefaults", - platform_apis: true, + sdk_version: "system_current", srcs: [ "src/**/*.java", ":framework-tethering-shared-srcs", @@ -29,6 +29,7 @@ java_defaults { "netlink-client", "networkstack-aidl-interfaces-unstable-java", "android.hardware.tetheroffload.control-V1.0-java", + "net-utils-framework-common", ], libs: [ "framework-tethering", @@ -80,7 +81,7 @@ cc_library { // Common defaults for compiling the actual APK. java_defaults { name: "TetheringAppDefaults", - platform_apis: true, + sdk_version: "system_current", privileged: true, // Build system doesn't track transitive dependeicies for jni_libs, list all the dependencies // explicitly. diff --git a/packages/Tethering/AndroidManifest.xml b/packages/Tethering/AndroidManifest.xml index 5a71eb23abad..c71d0d7bc543 100644 --- a/packages/Tethering/AndroidManifest.xml +++ b/packages/Tethering/AndroidManifest.xml @@ -36,6 +36,7 @@ <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" /> <uses-permission android:name="android.permission.TETHER_PRIVILEGED" /> <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" /> + <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" /> <uses-permission android:name="android.permission.WRITE_SETTINGS" /> <application diff --git a/packages/Tethering/res/values/config.xml b/packages/Tethering/res/values/config.xml index ca290c637773..19e8d696c39a 100644 --- a/packages/Tethering/res/values/config.xml +++ b/packages/Tethering/res/values/config.xml @@ -38,8 +38,9 @@ <!-- List of regexpressions describing the interface (if any) that represent tetherable Wifi P2P interfaces. If the device doesn't want to support tethering over Wifi P2p this - should be empty. An example would be "p2p-p2p.*" --> + should be empty. An example would be "p2p-p2p\\d-.*" --> <string-array translatable="false" name="config_tether_wifi_p2p_regexs"> + <item>"p2p-p2p\\d-.*"</item> </string-array> <!-- List of regexpressions describing the interface (if any) that represent tetherable @@ -100,7 +101,7 @@ <!-- If the mobile hotspot feature requires provisioning, a package name and class name can be provided to launch a supported application that provisions the devices. - EntitlementManager will send an inent to Settings with the specified package name and + EntitlementManager will send an intent to Settings with the specified package name and class name in extras to launch provision app. TODO: note what extras here. diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java index 6ac467e39a9d..4306cf0bd5f3 100644 --- a/packages/Tethering/src/android/net/ip/IpServer.java +++ b/packages/Tethering/src/android/net/ip/IpServer.java @@ -26,7 +26,6 @@ import static android.net.util.TetheringMessageBase.BASE_IPSERVER; import android.net.INetd; import android.net.INetworkStackStatusCallback; -import android.net.INetworkStatsService; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; @@ -176,7 +175,6 @@ public class IpServer extends StateMachine { private final SharedLog mLog; private final INetd mNetd; - private final INetworkStatsService mStatsService; private final Callback mCallback; private final InterfaceController mInterfaceCtrl; @@ -208,12 +206,10 @@ public class IpServer extends StateMachine { public IpServer( String ifaceName, Looper looper, int interfaceType, SharedLog log, - INetd netd, INetworkStatsService statsService, Callback callback, - boolean usingLegacyDhcp, Dependencies deps) { + INetd netd, Callback callback, boolean usingLegacyDhcp, Dependencies deps) { super(ifaceName, looper); mLog = log.forSubComponent(ifaceName); mNetd = netd; - mStatsService = statsService; mCallback = callback; mInterfaceCtrl = new InterfaceController(ifaceName, mNetd, mLog); mIfaceName = ifaceName; @@ -882,12 +878,6 @@ public class IpServer extends StateMachine { // to remove their rules, which generates errors. // Just do the best we can. try { - // About to tear down NAT; gather remaining statistics. - mStatsService.forceUpdate(); - } catch (Exception e) { - mLog.e("Exception in forceUpdate: " + e.toString()); - } - try { mNetd.ipfwdRemoveInterfaceForward(mIfaceName, upstreamIface); } catch (RemoteException | ServiceSpecificException e) { mLog.e("Exception in ipfwdRemoveInterfaceForward: " + e.toString()); diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java b/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java index 4e2a2c1c7af7..1cabc8d0b5b7 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java @@ -27,8 +27,6 @@ import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKONWN; import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; import static android.net.TetheringManager.TETHER_ERROR_PROVISION_FAILED; -import static com.android.internal.R.string.config_wifi_tether_enable; - import android.app.AlarmManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; @@ -36,7 +34,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.res.Resources; import android.net.util.SharedLog; import android.os.Bundle; import android.os.Handler; @@ -54,6 +51,7 @@ import android.util.SparseIntArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.StateMachine; +import com.android.networkstack.tethering.R; import java.io.PrintWriter; @@ -75,9 +73,7 @@ public class EntitlementManager { "com.android.server.connectivity.tethering.PROVISIONING_RECHECK_ALARM"; private static final String EXTRA_SUBID = "subId"; - // {@link ComponentName} of the Service used to run tether provisioning. - private static final ComponentName TETHER_SERVICE = ComponentName.unflattenFromString( - Resources.getSystem().getString(config_wifi_tether_enable)); + private final ComponentName mSilentProvisioningService; private static final int MS_PER_HOUR = 60 * 60 * 1000; private static final int EVENT_START_PROVISIONING = 0; private static final int EVENT_STOP_PROVISIONING = 1; @@ -122,6 +118,8 @@ public class EntitlementManager { mHandler = new EntitlementHandler(masterHandler.getLooper()); mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_PROVISIONING_ALARM), null, mHandler); + mSilentProvisioningService = ComponentName.unflattenFromString( + mContext.getResources().getString(R.string.config_wifi_tether_enable)); } public void setOnUiEntitlementFailedListener(final OnUiEntitlementFailedListener listener) { @@ -377,7 +375,7 @@ public class EntitlementManager { intent.putExtra(EXTRA_RUN_PROVISION, true); intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver); intent.putExtra(EXTRA_SUBID, subId); - intent.setComponent(TETHER_SERVICE); + intent.setComponent(mSilentProvisioningService); // Only admin user can change tethering and SilentTetherProvisioning don't need to // show UI, it is fine to always start setting's background service as system user. mContext.startService(intent); diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java index ce7c2a669f0a..cc36f4a9c516 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java @@ -16,35 +16,40 @@ package com.android.server.connectivity.tethering; +import static android.net.NetworkStats.DEFAULT_NETWORK_NO; +import static android.net.NetworkStats.METERED_NO; +import static android.net.NetworkStats.ROAMING_NO; import static android.net.NetworkStats.SET_DEFAULT; -import static android.net.NetworkStats.STATS_PER_UID; import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; -import static android.net.TrafficStats.UID_TETHERING; +import static android.net.NetworkStats.UID_TETHERING; import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.usage.NetworkStatsManager; import android.content.ContentResolver; -import android.net.ITetheringStatsProvider; import android.net.InetAddresses; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.NetworkStats; +import android.net.NetworkStats.Entry; import android.net.RouteInfo; import android.net.netlink.ConntrackMessage; import android.net.netlink.NetlinkConstants; import android.net.netlink.NetlinkSocket; +import android.net.netstats.provider.AbstractNetworkStatsProvider; +import android.net.netstats.provider.NetworkStatsProviderCallback; import android.net.util.SharedLog; import android.os.Handler; -import android.os.INetworkManagementService; -import android.os.Looper; -import android.os.RemoteException; -import android.os.SystemClock; import android.provider.Settings; import android.system.ErrnoException; import android.system.OsConstants; import android.text.TextUtils; +import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; import com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats; @@ -73,13 +78,19 @@ public class OffloadController { private static final String ANYIP = "0.0.0.0"; private static final ForwardedStats EMPTY_STATS = new ForwardedStats(); + @VisibleForTesting + enum StatsType { + STATS_PER_IFACE, + STATS_PER_UID, + } + private enum UpdateType { IF_NEEDED, FORCE }; private final Handler mHandler; private final OffloadHardwareInterface mHwInterface; private final ContentResolver mContentResolver; - private final INetworkManagementService mNms; - private final ITetheringStatsProvider mStatsProvider; + private final @NonNull OffloadTetheringStatsProvider mStatsProvider; + private final @Nullable NetworkStatsProviderCallback mStatsProviderCb; private final SharedLog mLog; private final HashMap<String, LinkProperties> mDownstreams; private boolean mConfigInitialized; @@ -109,22 +120,23 @@ public class OffloadController { private int mNatUpdateNetlinkErrors; public OffloadController(Handler h, OffloadHardwareInterface hwi, - ContentResolver contentResolver, INetworkManagementService nms, SharedLog log) { + ContentResolver contentResolver, NetworkStatsManager nsm, SharedLog log) { mHandler = h; mHwInterface = hwi; mContentResolver = contentResolver; - mNms = nms; mStatsProvider = new OffloadTetheringStatsProvider(); mLog = log.forSubComponent(TAG); mDownstreams = new HashMap<>(); mExemptPrefixes = new HashSet<>(); mLastLocalPrefixStrs = new HashSet<>(); - + NetworkStatsProviderCallback providerCallback = null; try { - mNms.registerTetheringStatsProvider(mStatsProvider, getClass().getSimpleName()); - } catch (RemoteException e) { - mLog.e("Cannot register offload stats provider: " + e); + providerCallback = nsm.registerNetworkStatsProvider( + getClass().getSimpleName(), mStatsProvider); + } catch (RuntimeException e) { + Log.wtf(TAG, "Cannot register offload stats provider: " + e); } + mStatsProviderCb = providerCallback; } /** Start hardware offload. */ @@ -173,7 +185,7 @@ public class OffloadController { // and we need to synchronize stats and limits between // software and hardware forwarding. updateStatsForAllUpstreams(); - forceTetherStatsPoll(); + mStatsProvider.pushTetherStats(); } @Override @@ -186,7 +198,7 @@ public class OffloadController { // limits set take into account any software tethering // traffic that has been happening in the meantime. updateStatsForAllUpstreams(); - forceTetherStatsPoll(); + mStatsProvider.pushTetherStats(); // [2] (Re)Push all state. computeAndPushLocalPrefixes(UpdateType.FORCE); pushAllDownstreamState(); @@ -204,14 +216,11 @@ public class OffloadController { // the HAL queued the callback. // TODO: rev the HAL so that it provides an interface name. - // Fetch current stats, so that when our notification reaches - // NetworkStatsService and triggers a poll, we will respond with - // current data (which will be above the limit that was reached). - // Note that if we just changed upstream, this is unnecessary but harmless. - // The stats for the previous upstream were already updated on this thread - // just after the upstream was changed, so they are also up-to-date. updateStatsForCurrentUpstream(); - forceTetherStatsPoll(); + mStatsProvider.pushTetherStats(); + // Push stats to service does not cause the service react to it immediately. + // Inform the service about limit reached. + if (mStatsProviderCb != null) mStatsProviderCb.onLimitReached(); } @Override @@ -253,42 +262,37 @@ public class OffloadController { return mConfigInitialized && mControlInitialized; } - private class OffloadTetheringStatsProvider extends ITetheringStatsProvider.Stub { - @Override - public NetworkStats getTetherStats(int how) { - // getTetherStats() is the only function in OffloadController that can be called from - // a different thread. Do not attempt to update stats by querying the offload HAL - // synchronously from a different thread than our Handler thread. http://b/64771555. - Runnable updateStats = () -> { - updateStatsForCurrentUpstream(); - }; - if (Looper.myLooper() == mHandler.getLooper()) { - updateStats.run(); - } else { - mHandler.post(updateStats); - } - - NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0); - NetworkStats.Entry entry = new NetworkStats.Entry(); - entry.set = SET_DEFAULT; - entry.tag = TAG_NONE; - entry.uid = (how == STATS_PER_UID) ? UID_TETHERING : UID_ALL; - - for (Map.Entry<String, ForwardedStats> kv : mForwardedStats.entrySet()) { - ForwardedStats value = kv.getValue(); - entry.iface = kv.getKey(); - entry.rxBytes = value.rxBytes; - entry.txBytes = value.txBytes; - stats.addEntry(entry); + @VisibleForTesting + class OffloadTetheringStatsProvider extends AbstractNetworkStatsProvider { + // These stats must only ever be touched on the handler thread. + @NonNull + private NetworkStats mIfaceStats = new NetworkStats(0L, 0); + @NonNull + private NetworkStats mUidStats = new NetworkStats(0L, 0); + + @VisibleForTesting + @NonNull + NetworkStats getTetherStats(@NonNull StatsType how) { + NetworkStats stats = new NetworkStats(0L, 0); + final int uid = (how == StatsType.STATS_PER_UID) ? UID_TETHERING : UID_ALL; + + for (final Map.Entry<String, ForwardedStats> kv : mForwardedStats.entrySet()) { + final ForwardedStats value = kv.getValue(); + final Entry entry = new Entry(kv.getKey(), uid, SET_DEFAULT, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, value.rxBytes, 0L, value.txBytes, 0L, 0L); + stats = stats.addValues(entry); } return stats; } @Override - public void setInterfaceQuota(String iface, long quotaBytes) { + public void setLimit(String iface, long quotaBytes) { + mLog.i("setLimit: " + iface + "," + quotaBytes); + // Listen for all iface is necessary since upstream might be changed after limit + // is set. mHandler.post(() -> { - if (quotaBytes == ITetheringStatsProvider.QUOTA_UNLIMITED) { + if (quotaBytes == QUOTA_UNLIMITED) { mInterfaceQuotas.remove(iface); } else { mInterfaceQuotas.put(iface, quotaBytes); @@ -296,6 +300,42 @@ public class OffloadController { maybeUpdateDataLimit(iface); }); } + + /** + * Push stats to service, but does not cause a force polling. Note that this can only be + * called on the handler thread. + */ + public void pushTetherStats() { + // TODO: remove the accumulated stats and report the diff from HAL directly. + if (null == mStatsProviderCb) return; + final NetworkStats ifaceDiff = + getTetherStats(StatsType.STATS_PER_IFACE).subtract(mIfaceStats); + final NetworkStats uidDiff = + getTetherStats(StatsType.STATS_PER_UID).subtract(mUidStats); + try { + mStatsProviderCb.onStatsUpdated(0 /* token */, ifaceDiff, uidDiff); + mIfaceStats = mIfaceStats.add(ifaceDiff); + mUidStats = mUidStats.add(uidDiff); + } catch (RuntimeException e) { + mLog.e("Cannot report network stats: ", e); + } + } + + @Override + public void requestStatsUpdate(int token) { + mLog.i("requestStatsUpdate: " + token); + // Do not attempt to update stats by querying the offload HAL + // synchronously from a different thread than the Handler thread. http://b/64771555. + mHandler.post(() -> { + updateStatsForCurrentUpstream(); + pushTetherStats(); + }); + } + + @Override + public void setAlert(long quotaBytes) { + // TODO: Ask offload HAL to notify alert without stopping traffic. + } } private String currentUpstreamInterface() { @@ -353,14 +393,6 @@ public class OffloadController { } } - private void forceTetherStatsPoll() { - try { - mNms.tetherLimitReached(mStatsProvider); - } catch (RemoteException e) { - mLog.e("Cannot report data limit reached: " + e); - } - } - /** Set current tethering upstream LinkProperties. */ public void setUpstreamLinkProperties(LinkProperties lp) { if (!started() || Objects.equals(mUpstreamLinkProperties, lp)) return; diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java index 4a8ef1f92754..90b9d3f148dc 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java @@ -29,6 +29,8 @@ import android.os.Handler; import android.os.RemoteException; import android.system.OsConstants; +import com.android.internal.annotations.VisibleForTesting; + import java.util.ArrayList; @@ -91,6 +93,12 @@ public class OffloadHardwareInterface { txBytes = 0; } + @VisibleForTesting + public ForwardedStats(long rxBytes, long txBytes) { + this.rxBytes = rxBytes; + this.txBytes = txBytes; + } + /** Add Tx/Rx bytes. */ public void add(ForwardedStats other) { rxBytes += other.rxBytes; diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java index 038d7ae72a1a..37e0cc107b58 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java @@ -20,6 +20,7 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.hardware.usb.UsbManager.USB_CONFIGURED; import static android.hardware.usb.UsbManager.USB_CONNECTED; import static android.hardware.usb.UsbManager.USB_FUNCTION_RNDIS; +import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.EXTRA_NETWORK_INFO; import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED; @@ -53,6 +54,7 @@ import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; +import android.app.usage.NetworkStatsManager; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothPan; import android.bluetooth.BluetoothProfile; @@ -63,9 +65,8 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; import android.hardware.usb.UsbManager; +import android.net.ConnectivityManager; import android.net.INetd; -import android.net.INetworkPolicyManager; -import android.net.INetworkStatsService; import android.net.ITetheringEventCallback; import android.net.IpPrefix; import android.net.LinkAddress; @@ -176,8 +177,6 @@ public class Tethering { private final Context mContext; private final ArrayMap<String, TetherState> mTetherStates; private final BroadcastReceiver mStateReceiver; - private final INetworkStatsService mStatsService; - private final INetworkPolicyManager mPolicyManager; private final Looper mLooper; private final StateMachine mTetherMasterSM; private final OffloadController mOffloadController; @@ -207,13 +206,12 @@ public class Tethering { private boolean mWifiTetherRequested; private Network mTetherUpstream; private TetherStatesParcel mTetherStatesParcel; + private boolean mDataSaverEnabled = false; public Tethering(TetheringDependencies deps) { mLog.mark("Tethering.constructed"); mDeps = deps; mContext = mDeps.getContext(); - mStatsService = mDeps.getINetworkStatsService(); - mPolicyManager = mDeps.getINetworkPolicyManager(); mNetd = mDeps.getINetd(mContext); mLooper = mDeps.getTetheringLooper(); @@ -224,10 +222,12 @@ public class Tethering { mTetherMasterSM = new TetherMasterSM("TetherMaster", mLooper, deps); mTetherMasterSM.start(); + final NetworkStatsManager statsManager = + (NetworkStatsManager) mContext.getSystemService(Context.NETWORK_STATS_SERVICE); mHandler = mTetherMasterSM.getHandler(); mOffloadController = new OffloadController(mHandler, mDeps.getOffloadHardwareInterface(mHandler, mLog), mContext.getContentResolver(), - mDeps.getINetworkManagementService(), mLog); + statsManager, mLog); mUpstreamNetworkMonitor = mDeps.getUpstreamNetworkMonitor(mContext, mTetherMasterSM, mLog, TetherMasterSM.EVENT_UPSTREAM_CALLBACK); mForwardedDownstreams = new HashSet<>(); @@ -264,7 +264,7 @@ public class Tethering { } final UserManager userManager = (UserManager) mContext.getSystemService( - Context.USER_SERVICE); + Context.USER_SERVICE); mTetheringRestriction = new UserRestrictionActionListener(userManager, this); final TetheringThreadExecutor executor = new TetheringThreadExecutor(mHandler); mActiveDataSubIdListener = new ActiveDataSubIdListener(executor); @@ -288,6 +288,7 @@ public class Tethering { filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); filter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); filter.addAction(UserManager.ACTION_USER_RESTRICTIONS_CHANGED); + filter.addAction(ACTION_RESTRICT_BACKGROUND_CHANGED); mContext.registerReceiver(mStateReceiver, filter, null, handler); } @@ -484,7 +485,7 @@ public class Tethering { } private void setBluetoothTethering(final boolean enable, final ResultReceiver receiver) { - final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + final BluetoothAdapter adapter = mDeps.getBluetoothAdapter(); if (adapter == null || !adapter.isEnabled()) { Log.w(TAG, "Tried to enable bluetooth tethering with null or disabled adapter. null: " + (adapter == null)); @@ -740,8 +741,7 @@ public class Tethering { .setContentIntent(pi); mLastNotificationId = id; - notificationManager.notify(null, mLastNotificationId, - mTetheredNotificationBuilder.buildInto(new Notification())); + notificationManager.notify(null, mLastNotificationId, mTetheredNotificationBuilder.build()); } @VisibleForTesting @@ -775,6 +775,9 @@ public class Tethering { } else if (action.equals(UserManager.ACTION_USER_RESTRICTIONS_CHANGED)) { mLog.log("OBSERVED user restrictions changed"); handleUserRestrictionAction(); + } else if (action.equals(ACTION_RESTRICT_BACKGROUND_CHANGED)) { + mLog.log("OBSERVED data saver changed"); + handleDataSaverChanged(); } } @@ -885,6 +888,20 @@ public class Tethering { private void handleUserRestrictionAction() { mTetheringRestriction.onUserRestrictionsChanged(); } + + private void handleDataSaverChanged() { + final ConnectivityManager connMgr = (ConnectivityManager) mContext.getSystemService( + Context.CONNECTIVITY_SERVICE); + final boolean isDataSaverEnabled = connMgr.getRestrictBackgroundStatus() + != ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED; + + if (mDataSaverEnabled == isDataSaverEnabled) return; + + mDataSaverEnabled = isDataSaverEnabled; + if (mDataSaverEnabled) { + untetherAll(); + } + } } @VisibleForTesting @@ -1982,15 +1999,6 @@ public class Tethering { mLog.log(String.format("OBSERVED iface=%s state=%s error=%s", iface, state, error)); - try { - // Notify that we're tethering (or not) this interface. - // This is how data saver for instance knows if the user explicitly - // turned on tethering (thus keeping us from being in data saver mode). - mPolicyManager.onTetheringChanged(iface, state == IpServer.STATE_TETHERED); - } catch (RemoteException e) { - // Not really very much we can do here. - } - // If TetherMasterSM is in ErrorState, TetherMasterSM stays there. // Thus we give a chance for TetherMasterSM to recover to InitialState // by sending CMD_CLEAR_ERROR @@ -2054,7 +2062,7 @@ public class Tethering { mLog.log("adding TetheringInterfaceStateMachine for: " + iface); final TetherState tetherState = new TetherState( - new IpServer(iface, mLooper, interfaceType, mLog, mNetd, mStatsService, + new IpServer(iface, mLooper, interfaceType, mLog, mNetd, makeControlCallback(), mConfig.enableLegacyDhcpServer, mDeps.getIpServerDependencies())); mTetherStates.put(iface, tetherState); diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java index dbe789288c6f..068c346fbfc1 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java @@ -23,18 +23,6 @@ import static android.net.ConnectivityManager.TYPE_MOBILE_DUN; import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; -import static com.android.internal.R.array.config_mobile_hotspot_provision_app; -import static com.android.internal.R.array.config_tether_bluetooth_regexs; -import static com.android.internal.R.array.config_tether_dhcp_range; -import static com.android.internal.R.array.config_tether_upstream_types; -import static com.android.internal.R.array.config_tether_usb_regexs; -import static com.android.internal.R.array.config_tether_wifi_p2p_regexs; -import static com.android.internal.R.array.config_tether_wifi_regexs; -import static com.android.internal.R.bool.config_tether_upstream_automatic; -import static com.android.internal.R.integer.config_mobile_hotspot_provision_check_period; -import static com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui; -import static com.android.networkstack.tethering.R.bool.config_tether_enable_legacy_dhcp_server; - import android.content.Context; import android.content.res.Resources; import android.net.TetheringConfigurationParcel; @@ -45,6 +33,7 @@ import android.telephony.TelephonyManager; import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; +import com.android.networkstack.tethering.R; import java.io.PrintWriter; import java.util.ArrayList; @@ -113,27 +102,30 @@ public class TetheringConfiguration { activeDataSubId = id; Resources res = getResources(ctx, activeDataSubId); - tetherableUsbRegexs = getResourceStringArray(res, config_tether_usb_regexs); + tetherableUsbRegexs = getResourceStringArray(res, R.array.config_tether_usb_regexs); // TODO: Evaluate deleting this altogether now that Wi-Fi always passes // us an interface name. Careful consideration needs to be given to // implications for Settings and for provisioning checks. - tetherableWifiRegexs = getResourceStringArray(res, config_tether_wifi_regexs); - tetherableWifiP2pRegexs = getResourceStringArray(res, config_tether_wifi_p2p_regexs); - tetherableBluetoothRegexs = getResourceStringArray(res, config_tether_bluetooth_regexs); + tetherableWifiRegexs = getResourceStringArray(res, R.array.config_tether_wifi_regexs); + tetherableWifiP2pRegexs = getResourceStringArray( + res, R.array.config_tether_wifi_p2p_regexs); + tetherableBluetoothRegexs = getResourceStringArray( + res, R.array.config_tether_bluetooth_regexs); isDunRequired = checkDunRequired(ctx); - chooseUpstreamAutomatically = getResourceBoolean(res, config_tether_upstream_automatic); + chooseUpstreamAutomatically = getResourceBoolean( + res, R.bool.config_tether_upstream_automatic); preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(res, isDunRequired); legacyDhcpRanges = getLegacyDhcpRanges(res); defaultIPv4DNS = copy(DEFAULT_IPV4_DNS); enableLegacyDhcpServer = getEnableLegacyDhcpServer(res); - provisioningApp = getResourceStringArray(res, config_mobile_hotspot_provision_app); + provisioningApp = getResourceStringArray(res, R.array.config_mobile_hotspot_provision_app); provisioningAppNoUi = getProvisioningAppNoUi(res); provisioningCheckPeriod = getResourceInteger(res, - config_mobile_hotspot_provision_check_period, + R.integer.config_mobile_hotspot_provision_check_period, 0 /* No periodic re-check */); configLog.log(toString()); @@ -248,7 +240,7 @@ public class TetheringConfiguration { } private static Collection<Integer> getUpstreamIfaceTypes(Resources res, boolean dunRequired) { - final int[] ifaceTypes = res.getIntArray(config_tether_upstream_types); + final int[] ifaceTypes = res.getIntArray(R.array.config_tether_upstream_types); final ArrayList<Integer> upstreamIfaceTypes = new ArrayList<>(ifaceTypes.length); for (int i : ifaceTypes) { switch (i) { @@ -298,7 +290,7 @@ public class TetheringConfiguration { } private static String[] getLegacyDhcpRanges(Resources res) { - final String[] fromResource = getResourceStringArray(res, config_tether_dhcp_range); + final String[] fromResource = getResourceStringArray(res, R.array.config_tether_dhcp_range); if ((fromResource.length > 0) && (fromResource.length % 2 == 0)) { return fromResource; } @@ -307,7 +299,7 @@ public class TetheringConfiguration { private static String getProvisioningAppNoUi(Resources res) { try { - return res.getString(config_mobile_hotspot_provision_app_no_ui); + return res.getString(R.string.config_mobile_hotspot_provision_app_no_ui); } catch (Resources.NotFoundException e) { return ""; } @@ -339,7 +331,7 @@ public class TetheringConfiguration { } private boolean getEnableLegacyDhcpServer(final Resources res) { - return getResourceBoolean(res, config_tether_enable_legacy_dhcp_server) + return getResourceBoolean(res, R.bool.config_tether_enable_legacy_dhcp_server) || getDeviceConfigBoolean(TETHER_ENABLE_LEGACY_DHCP_SERVER); } diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java index b16b3294a112..e019c3aca26a 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java @@ -16,18 +16,15 @@ package com.android.server.connectivity.tethering; +import android.bluetooth.BluetoothAdapter; import android.content.Context; import android.net.INetd; -import android.net.INetworkPolicyManager; -import android.net.INetworkStatsService; import android.net.NetworkRequest; import android.net.ip.IpServer; import android.net.util.SharedLog; import android.os.Handler; import android.os.IBinder; -import android.os.INetworkManagementService; import android.os.Looper; -import android.os.ServiceManager; import com.android.internal.util.StateMachine; @@ -97,33 +94,6 @@ public abstract class TetheringDependencies { } /** - * Get a reference to INetworkManagementService to registerTetheringStatsProvider from - * OffloadController. Note: This should be removed soon by Usage refactor work in R - * development cycle. - */ - public INetworkManagementService getINetworkManagementService() { - return INetworkManagementService.Stub.asInterface( - ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); - } - - /** - * Get a reference to INetworkStatsService to force update tethering usage. - * Note: This should be removed in R development cycle. - */ - public INetworkStatsService getINetworkStatsService() { - return INetworkStatsService.Stub.asInterface( - ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); - } - - /** - * Get a reference to INetworkPolicyManager to be used by tethering. - */ - public INetworkPolicyManager getINetworkPolicyManager() { - return INetworkPolicyManager.Stub.asInterface( - ServiceManager.getService(Context.NETWORK_POLICY_SERVICE)); - } - - /** * Get a reference to INetd to be used by tethering. */ public INetd getINetd(Context context) { @@ -140,4 +110,9 @@ public abstract class TetheringDependencies { * Get Context of TetheringSerice. */ public abstract Context getContext(); + + /** + * Get a reference to BluetoothAdapter to be used by tethering. + */ + public abstract BluetoothAdapter getBluetoothAdapter(); } diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java index d5cdd8a004dc..4dd68301f9fa 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java @@ -22,6 +22,8 @@ import android.net.NetworkCapabilities; import android.net.RouteInfo; import android.net.util.InterfaceSet; +import com.android.net.module.util.NetUtils; + import java.net.InetAddress; import java.net.UnknownHostException; @@ -85,7 +87,7 @@ public final class TetheringInterfaceUtils { private static String getInterfaceForDestination(LinkProperties lp, InetAddress dst) { final RouteInfo ri = (lp != null) - ? RouteInfo.selectBestRoute(lp.getAllRoutes(), dst) + ? NetUtils.selectBestRoute(lp.getAllRoutes(), dst) : null; return (ri != null) ? ri.getInterface() : null; } diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java index e4e4a090603d..cb7d3920e693 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java @@ -24,6 +24,7 @@ import static android.net.TetheringManager.TETHER_ERROR_UNSUPPORTED; import static android.net.dhcp.IDhcpServer.STATUS_UNKNOWN_ERROR; import android.app.Service; +import android.bluetooth.BluetoothAdapter; import android.content.Context; import android.content.Intent; import android.net.IIntResultListener; @@ -42,7 +43,6 @@ import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.os.ResultReceiver; -import android.os.ServiceManager; import android.os.SystemProperties; import android.os.UserManager; import android.provider.Settings; @@ -363,7 +363,7 @@ public class TetheringService extends Service { IBinder connector; try { final long before = System.currentTimeMillis(); - while ((connector = ServiceManager.getService( + while ((connector = (IBinder) mContext.getSystemService( Context.NETWORK_STACK_SERVICE)) == null) { if (System.currentTimeMillis() - before > NETWORKSTACK_TIMEOUT_MS) { Log.wtf(TAG, "Timeout, fail to get INetworkStackConnector"); @@ -377,6 +377,11 @@ public class TetheringService extends Service { } return INetworkStackConnector.Stub.asInterface(connector); } + + @Override + public BluetoothAdapter getBluetoothAdapter() { + return BluetoothAdapter.getDefaultAdapter(); + } }; } return mDeps; diff --git a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java index 65a0ac13a84b..1f50b6bf7fb3 100644 --- a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java +++ b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java @@ -52,7 +52,6 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.net.INetd; -import android.net.INetworkStatsService; import android.net.InterfaceConfigurationParcel; import android.net.IpPrefix; import android.net.LinkAddress; @@ -99,7 +98,6 @@ public class IpServerTest { private static final int MAKE_DHCPSERVER_TIMEOUT_MS = 1000; @Mock private INetd mNetd; - @Mock private INetworkStatsService mStatsService; @Mock private IpServer.Callback mCallback; @Mock private SharedLog mSharedLog; @Mock private IDhcpServer mDhcpServer; @@ -139,13 +137,13 @@ public class IpServerTest { mInterfaceConfiguration.prefixLength = BLUETOOTH_DHCP_PREFIX_LENGTH; } mIpServer = new IpServer( - IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd, mStatsService, + IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd, mCallback, usingLegacyDhcp, mDependencies); mIpServer.start(); // Starting the state machine always puts us in a consistent state and notifies // the rest of the world that we've changed from an unknown to available state. mLooper.dispatchAll(); - reset(mNetd, mStatsService, mCallback); + reset(mNetd, mCallback); when(mRaDaemon.start()).thenReturn(true); } @@ -162,7 +160,7 @@ public class IpServerTest { if (upstreamIface != null) { dispatchTetherConnectionChanged(upstreamIface); } - reset(mNetd, mStatsService, mCallback); + reset(mNetd, mCallback); } @Before public void setUp() throws Exception { @@ -173,13 +171,13 @@ public class IpServerTest { @Test public void startsOutAvailable() { mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(), TETHERING_BLUETOOTH, mSharedLog, - mNetd, mStatsService, mCallback, false /* usingLegacyDhcp */, mDependencies); + mNetd, mCallback, false /* usingLegacyDhcp */, mDependencies); mIpServer.start(); mLooper.dispatchAll(); verify(mCallback).updateInterfaceState( mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR); verify(mCallback).updateLinkProperties(eq(mIpServer), any(LinkProperties.class)); - verifyNoMoreInteractions(mCallback, mNetd, mStatsService); + verifyNoMoreInteractions(mCallback, mNetd); } @Test @@ -198,7 +196,7 @@ public class IpServerTest { // None of these commands should trigger us to request action from // the rest of the system. dispatchCommand(command); - verifyNoMoreInteractions(mNetd, mStatsService, mCallback); + verifyNoMoreInteractions(mNetd, mCallback); } } @@ -210,7 +208,7 @@ public class IpServerTest { verify(mCallback).updateInterfaceState( mIpServer, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR); verify(mCallback).updateLinkProperties(eq(mIpServer), any(LinkProperties.class)); - verifyNoMoreInteractions(mNetd, mStatsService, mCallback); + verifyNoMoreInteractions(mNetd, mCallback); } @Test @@ -228,7 +226,7 @@ public class IpServerTest { mIpServer, STATE_TETHERED, TETHER_ERROR_NO_ERROR); inOrder.verify(mCallback).updateLinkProperties( eq(mIpServer), any(LinkProperties.class)); - verifyNoMoreInteractions(mNetd, mStatsService, mCallback); + verifyNoMoreInteractions(mNetd, mCallback); } @Test @@ -236,7 +234,7 @@ public class IpServerTest { initTetheredStateMachine(TETHERING_BLUETOOTH, null); dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED); - InOrder inOrder = inOrder(mNetd, mStatsService, mCallback); + InOrder inOrder = inOrder(mNetd, mCallback); inOrder.verify(mNetd).tetherApplyDnsInterfaces(); inOrder.verify(mNetd).tetherInterfaceRemove(IFACE_NAME); inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME); @@ -245,7 +243,7 @@ public class IpServerTest { mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR); inOrder.verify(mCallback).updateLinkProperties( eq(mIpServer), any(LinkProperties.class)); - verifyNoMoreInteractions(mNetd, mStatsService, mCallback); + verifyNoMoreInteractions(mNetd, mCallback); } @Test @@ -265,7 +263,7 @@ public class IpServerTest { inOrder.verify(mCallback).updateLinkProperties( eq(mIpServer), mLinkPropertiesCaptor.capture()); assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue()); - verifyNoMoreInteractions(mNetd, mStatsService, mCallback); + verifyNoMoreInteractions(mNetd, mCallback); } @Test @@ -285,7 +283,7 @@ public class IpServerTest { inOrder.verify(mCallback).updateLinkProperties( eq(mIpServer), mLinkPropertiesCaptor.capture()); assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue()); - verifyNoMoreInteractions(mNetd, mStatsService, mCallback); + verifyNoMoreInteractions(mNetd, mCallback); } @Test @@ -298,7 +296,7 @@ public class IpServerTest { InOrder inOrder = inOrder(mNetd); inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE); inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE); - verifyNoMoreInteractions(mNetd, mStatsService, mCallback); + verifyNoMoreInteractions(mNetd, mCallback); } @Test @@ -306,13 +304,12 @@ public class IpServerTest { initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE); dispatchTetherConnectionChanged(UPSTREAM_IFACE2); - InOrder inOrder = inOrder(mNetd, mStatsService); - inOrder.verify(mStatsService).forceUpdate(); + InOrder inOrder = inOrder(mNetd); inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE); inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE); inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2); inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2); - verifyNoMoreInteractions(mNetd, mStatsService, mCallback); + verifyNoMoreInteractions(mNetd, mCallback); } @Test @@ -322,12 +319,10 @@ public class IpServerTest { doThrow(RemoteException.class).when(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2); dispatchTetherConnectionChanged(UPSTREAM_IFACE2); - InOrder inOrder = inOrder(mNetd, mStatsService); - inOrder.verify(mStatsService).forceUpdate(); + InOrder inOrder = inOrder(mNetd); inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE); inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE); inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2); - inOrder.verify(mStatsService).forceUpdate(); inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2); inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE2); } @@ -340,13 +335,11 @@ public class IpServerTest { IFACE_NAME, UPSTREAM_IFACE2); dispatchTetherConnectionChanged(UPSTREAM_IFACE2); - InOrder inOrder = inOrder(mNetd, mStatsService); - inOrder.verify(mStatsService).forceUpdate(); + InOrder inOrder = inOrder(mNetd); inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE); inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE); inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2); inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2); - inOrder.verify(mStatsService).forceUpdate(); inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2); inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE2); } @@ -356,8 +349,7 @@ public class IpServerTest { initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE); dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED); - InOrder inOrder = inOrder(mNetd, mStatsService, mCallback); - inOrder.verify(mStatsService).forceUpdate(); + InOrder inOrder = inOrder(mNetd, mCallback); inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE); inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE); inOrder.verify(mNetd).tetherApplyDnsInterfaces(); @@ -368,7 +360,7 @@ public class IpServerTest { mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR); inOrder.verify(mCallback).updateLinkProperties( eq(mIpServer), any(LinkProperties.class)); - verifyNoMoreInteractions(mNetd, mStatsService, mCallback); + verifyNoMoreInteractions(mNetd, mCallback); } @Test @@ -435,11 +427,11 @@ public class IpServerTest { public void ignoresDuplicateUpstreamNotifications() throws Exception { initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE); - verifyNoMoreInteractions(mNetd, mStatsService, mCallback); + verifyNoMoreInteractions(mNetd, mCallback); for (int i = 0; i < 5; i++) { dispatchTetherConnectionChanged(UPSTREAM_IFACE); - verifyNoMoreInteractions(mNetd, mStatsService, mCallback); + verifyNoMoreInteractions(mNetd, mCallback); } } diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java index 79bba7f6e663..4f0746199786 100644 --- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java @@ -27,7 +27,6 @@ import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; -import static com.android.networkstack.tethering.R.bool.config_tether_enable_legacy_dhcp_server; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -56,10 +55,10 @@ import android.telephony.CarrierConfigManager; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.internal.R; import com.android.internal.util.State; import com.android.internal.util.StateMachine; import com.android.internal.util.test.BroadcastInterceptingContext; +import com.android.networkstack.tethering.R; import org.junit.After; import org.junit.Before; @@ -157,16 +156,18 @@ public final class EntitlementManagerTest { eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean())); when(mResources.getStringArray(R.array.config_tether_dhcp_range)) - .thenReturn(new String[0]); + .thenReturn(new String[0]); when(mResources.getStringArray(R.array.config_tether_usb_regexs)) - .thenReturn(new String[0]); + .thenReturn(new String[0]); when(mResources.getStringArray(R.array.config_tether_wifi_regexs)) - .thenReturn(new String[0]); + .thenReturn(new String[0]); when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs)) - .thenReturn(new String[0]); + .thenReturn(new String[0]); when(mResources.getIntArray(R.array.config_tether_upstream_types)) - .thenReturn(new int[0]); - when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false); + .thenReturn(new int[0]); + when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn( + false); + when(mResources.getString(R.string.config_wifi_tether_enable)).thenReturn(""); when(mLog.forSubComponent(anyString())).thenReturn(mLog); mMockContext = new MockContext(mContext); diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java index 7886ca6c132d..7e62e5aca993 100644 --- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java @@ -16,21 +16,26 @@ package com.android.server.connectivity.tethering; +import static android.net.NetworkStats.DEFAULT_NETWORK_NO; +import static android.net.NetworkStats.METERED_NO; +import static android.net.NetworkStats.ROAMING_NO; import static android.net.NetworkStats.SET_DEFAULT; -import static android.net.NetworkStats.STATS_PER_IFACE; -import static android.net.NetworkStats.STATS_PER_UID; import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; +import static android.net.NetworkStats.UID_TETHERING; import static android.net.RouteInfo.RTN_UNICAST; -import static android.net.TrafficStats.UID_TETHERING; import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED; +import static com.android.server.connectivity.tethering.OffloadController.StatsType.STATS_PER_IFACE; +import static com.android.server.connectivity.tethering.OffloadController.StatsType.STATS_PER_UID; import static com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats; import static com.android.testutils.MiscAssertsKt.assertContainsAll; import static com.android.testutils.MiscAssertsKt.assertThrows; +import static com.android.testutils.NetworkStatsUtilsKt.orderInsensitiveEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.anyObject; @@ -39,11 +44,14 @@ import static org.mockito.Matchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import android.annotation.NonNull; +import android.app.usage.NetworkStatsManager; import android.content.Context; import android.content.pm.ApplicationInfo; import android.net.ITetheringStatsProvider; @@ -51,10 +59,12 @@ import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.NetworkStats; +import android.net.NetworkStats.Entry; import android.net.RouteInfo; +import android.net.netstats.provider.AbstractNetworkStatsProvider; +import android.net.netstats.provider.NetworkStatsProviderCallback; import android.net.util.SharedLog; import android.os.Handler; -import android.os.INetworkManagementService; import android.os.Looper; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; @@ -97,11 +107,13 @@ public class OffloadControllerTest { @Mock private OffloadHardwareInterface mHardware; @Mock private ApplicationInfo mApplicationInfo; @Mock private Context mContext; - @Mock private INetworkManagementService mNMService; + @Mock private NetworkStatsManager mStatsManager; + @Mock private NetworkStatsProviderCallback mTetherStatsProviderCb; private final ArgumentCaptor<ArrayList> mStringArrayCaptor = ArgumentCaptor.forClass(ArrayList.class); - private final ArgumentCaptor<ITetheringStatsProvider.Stub> mTetherStatsProviderCaptor = - ArgumentCaptor.forClass(ITetheringStatsProvider.Stub.class); + private final ArgumentCaptor<OffloadController.OffloadTetheringStatsProvider> + mTetherStatsProviderCaptor = + ArgumentCaptor.forClass(OffloadController.OffloadTetheringStatsProvider.class); private final ArgumentCaptor<OffloadHardwareInterface.ControlCallback> mControlCallbackCaptor = ArgumentCaptor.forClass(OffloadHardwareInterface.ControlCallback.class); private MockContentResolver mContentResolver; @@ -114,6 +126,8 @@ public class OffloadControllerTest { mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); when(mContext.getContentResolver()).thenReturn(mContentResolver); FakeSettingsProvider.clearSettingsProvider(); + when(mStatsManager.registerNetworkStatsProvider(anyString(), any())) + .thenReturn(mTetherStatsProviderCb); } @After public void tearDown() throws Exception { @@ -139,9 +153,9 @@ public class OffloadControllerTest { private OffloadController makeOffloadController() throws Exception { OffloadController offload = new OffloadController(new Handler(Looper.getMainLooper()), - mHardware, mContentResolver, mNMService, new SharedLog("test")); - verify(mNMService).registerTetheringStatsProvider( - mTetherStatsProviderCaptor.capture(), anyString()); + mHardware, mContentResolver, mStatsManager, new SharedLog("test")); + verify(mStatsManager).registerNetworkStatsProvider(anyString(), + mTetherStatsProviderCaptor.capture()); return offload; } @@ -384,12 +398,11 @@ public class OffloadControllerTest { inOrder.verifyNoMoreInteractions(); } - private void assertNetworkStats(String iface, ForwardedStats stats, NetworkStats.Entry entry) { - assertEquals(iface, entry.iface); - assertEquals(stats.rxBytes, entry.rxBytes); - assertEquals(stats.txBytes, entry.txBytes); - assertEquals(SET_DEFAULT, entry.set); - assertEquals(TAG_NONE, entry.tag); + private static @NonNull Entry buildTestEntry(@NonNull OffloadController.StatsType how, + @NonNull String iface, long rxBytes, long txBytes) { + return new Entry(iface, how == STATS_PER_IFACE ? UID_ALL : UID_TETHERING, SET_DEFAULT, + TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, rxBytes, 0L, + txBytes, 0L, 0L); } @Test @@ -400,19 +413,16 @@ public class OffloadControllerTest { final OffloadController offload = makeOffloadController(); offload.start(); + final OffloadController.OffloadTetheringStatsProvider provider = + mTetherStatsProviderCaptor.getValue(); + final String ethernetIface = "eth1"; final String mobileIface = "rmnet_data0"; - ForwardedStats ethernetStats = new ForwardedStats(); - ethernetStats.rxBytes = 12345; - ethernetStats.txBytes = 54321; - - ForwardedStats mobileStats = new ForwardedStats(); - mobileStats.rxBytes = 999; - mobileStats.txBytes = 99999; - - when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(ethernetStats); - when(mHardware.getForwardedStats(eq(mobileIface))).thenReturn(mobileStats); + when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn( + new ForwardedStats(12345, 54321)); + when(mHardware.getForwardedStats(eq(mobileIface))).thenReturn( + new ForwardedStats(999, 99999)); InOrder inOrder = inOrder(mHardware); @@ -432,10 +442,35 @@ public class OffloadControllerTest { // Expect that we fetch stats from the previous upstream. inOrder.verify(mHardware, times(1)).getForwardedStats(eq(mobileIface)); - ethernetStats = new ForwardedStats(); - ethernetStats.rxBytes = 100000; - ethernetStats.txBytes = 100000; - when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(ethernetStats); + // Verify that the fetched stats are stored. + final NetworkStats ifaceStats = provider.getTetherStats(STATS_PER_IFACE); + final NetworkStats uidStats = provider.getTetherStats(STATS_PER_UID); + final NetworkStats expectedIfaceStats = new NetworkStats(0L, 2) + .addValues(buildTestEntry(STATS_PER_IFACE, mobileIface, 999, 99999)) + .addValues(buildTestEntry(STATS_PER_IFACE, ethernetIface, 12345, 54321)); + + final NetworkStats expectedUidStats = new NetworkStats(0L, 2) + .addValues(buildTestEntry(STATS_PER_UID, mobileIface, 999, 99999)) + .addValues(buildTestEntry(STATS_PER_UID, ethernetIface, 12345, 54321)); + + assertTrue(orderInsensitiveEquals(expectedIfaceStats, ifaceStats)); + assertTrue(orderInsensitiveEquals(expectedUidStats, uidStats)); + + final ArgumentCaptor<NetworkStats> ifaceStatsCaptor = ArgumentCaptor.forClass( + NetworkStats.class); + final ArgumentCaptor<NetworkStats> uidStatsCaptor = ArgumentCaptor.forClass( + NetworkStats.class); + + // Force pushing stats update to verify the stats reported. + provider.pushTetherStats(); + verify(mTetherStatsProviderCb, times(1)).onStatsUpdated(anyInt(), + ifaceStatsCaptor.capture(), uidStatsCaptor.capture()); + assertTrue(orderInsensitiveEquals(expectedIfaceStats, ifaceStatsCaptor.getValue())); + assertTrue(orderInsensitiveEquals(expectedUidStats, uidStatsCaptor.getValue())); + + + when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn( + new ForwardedStats(100000, 100000)); offload.setUpstreamLinkProperties(null); // Expect that we first clear the HAL's upstream parameters. inOrder.verify(mHardware, times(1)).setUpstreamParameters( @@ -443,37 +478,38 @@ public class OffloadControllerTest { // Expect that we fetch stats from the previous upstream. inOrder.verify(mHardware, times(1)).getForwardedStats(eq(ethernetIface)); - ITetheringStatsProvider provider = mTetherStatsProviderCaptor.getValue(); - NetworkStats stats = provider.getTetherStats(STATS_PER_IFACE); - NetworkStats perUidStats = provider.getTetherStats(STATS_PER_UID); - waitForIdle(); // There is no current upstream, so no stats are fetched. inOrder.verify(mHardware, never()).getForwardedStats(any()); inOrder.verifyNoMoreInteractions(); - assertEquals(2, stats.size()); - assertEquals(2, perUidStats.size()); - - NetworkStats.Entry entry = null; - for (int i = 0; i < stats.size(); i++) { - assertEquals(UID_ALL, stats.getValues(i, entry).uid); - assertEquals(UID_TETHERING, perUidStats.getValues(i, entry).uid); - } - - int ethernetPosition = ethernetIface.equals(stats.getValues(0, entry).iface) ? 0 : 1; - int mobilePosition = 1 - ethernetPosition; - - entry = stats.getValues(mobilePosition, entry); - assertNetworkStats(mobileIface, mobileStats, entry); - entry = perUidStats.getValues(mobilePosition, entry); - assertNetworkStats(mobileIface, mobileStats, entry); - - ethernetStats.rxBytes = 12345 + 100000; - ethernetStats.txBytes = 54321 + 100000; - entry = stats.getValues(ethernetPosition, entry); - assertNetworkStats(ethernetIface, ethernetStats, entry); - entry = perUidStats.getValues(ethernetPosition, entry); - assertNetworkStats(ethernetIface, ethernetStats, entry); + // Verify that the stored stats is accumulated. + final NetworkStats ifaceStatsAccu = provider.getTetherStats(STATS_PER_IFACE); + final NetworkStats uidStatsAccu = provider.getTetherStats(STATS_PER_UID); + final NetworkStats expectedIfaceStatsAccu = new NetworkStats(0L, 2) + .addValues(buildTestEntry(STATS_PER_IFACE, mobileIface, 999, 99999)) + .addValues(buildTestEntry(STATS_PER_IFACE, ethernetIface, 112345, 154321)); + + final NetworkStats expectedUidStatsAccu = new NetworkStats(0L, 2) + .addValues(buildTestEntry(STATS_PER_UID, mobileIface, 999, 99999)) + .addValues(buildTestEntry(STATS_PER_UID, ethernetIface, 112345, 154321)); + + assertTrue(orderInsensitiveEquals(expectedIfaceStatsAccu, ifaceStatsAccu)); + assertTrue(orderInsensitiveEquals(expectedUidStatsAccu, uidStatsAccu)); + + // Verify that only diff of stats is reported. + reset(mTetherStatsProviderCb); + provider.pushTetherStats(); + final NetworkStats expectedIfaceStatsDiff = new NetworkStats(0L, 2) + .addValues(buildTestEntry(STATS_PER_IFACE, mobileIface, 0, 0)) + .addValues(buildTestEntry(STATS_PER_IFACE, ethernetIface, 100000, 100000)); + + final NetworkStats expectedUidStatsDiff = new NetworkStats(0L, 2) + .addValues(buildTestEntry(STATS_PER_UID, mobileIface, 0, 0)) + .addValues(buildTestEntry(STATS_PER_UID, ethernetIface, 100000, 100000)); + verify(mTetherStatsProviderCb, times(1)).onStatsUpdated(anyInt(), + ifaceStatsCaptor.capture(), uidStatsCaptor.capture()); + assertTrue(orderInsensitiveEquals(expectedIfaceStatsDiff, ifaceStatsCaptor.getValue())); + assertTrue(orderInsensitiveEquals(expectedUidStatsDiff, uidStatsCaptor.getValue())); } @Test @@ -493,19 +529,19 @@ public class OffloadControllerTest { lp.setInterfaceName(ethernetIface); offload.setUpstreamLinkProperties(lp); - ITetheringStatsProvider provider = mTetherStatsProviderCaptor.getValue(); + AbstractNetworkStatsProvider provider = mTetherStatsProviderCaptor.getValue(); final InOrder inOrder = inOrder(mHardware); when(mHardware.setUpstreamParameters(any(), any(), any(), any())).thenReturn(true); when(mHardware.setDataLimit(anyString(), anyLong())).thenReturn(true); // Applying an interface quota to the current upstream immediately sends it to the hardware. - provider.setInterfaceQuota(ethernetIface, ethernetLimit); + provider.setLimit(ethernetIface, ethernetLimit); waitForIdle(); inOrder.verify(mHardware).setDataLimit(ethernetIface, ethernetLimit); inOrder.verifyNoMoreInteractions(); // Applying an interface quota to another upstream does not take any immediate action. - provider.setInterfaceQuota(mobileIface, mobileLimit); + provider.setLimit(mobileIface, mobileLimit); waitForIdle(); inOrder.verify(mHardware, never()).setDataLimit(anyString(), anyLong()); @@ -518,7 +554,7 @@ public class OffloadControllerTest { // Setting a limit of ITetheringStatsProvider.QUOTA_UNLIMITED causes the limit to be set // to Long.MAX_VALUE. - provider.setInterfaceQuota(mobileIface, ITetheringStatsProvider.QUOTA_UNLIMITED); + provider.setLimit(mobileIface, ITetheringStatsProvider.QUOTA_UNLIMITED); waitForIdle(); inOrder.verify(mHardware).setDataLimit(mobileIface, Long.MAX_VALUE); @@ -526,7 +562,7 @@ public class OffloadControllerTest { when(mHardware.setUpstreamParameters(any(), any(), any(), any())).thenReturn(false); lp.setInterfaceName(ethernetIface); offload.setUpstreamLinkProperties(lp); - provider.setInterfaceQuota(mobileIface, mobileLimit); + provider.setLimit(mobileIface, mobileLimit); waitForIdle(); inOrder.verify(mHardware, never()).setDataLimit(anyString(), anyLong()); @@ -535,7 +571,7 @@ public class OffloadControllerTest { when(mHardware.setDataLimit(anyString(), anyLong())).thenReturn(false); lp.setInterfaceName(mobileIface); offload.setUpstreamLinkProperties(lp); - provider.setInterfaceQuota(mobileIface, mobileLimit); + provider.setLimit(mobileIface, mobileLimit); waitForIdle(); inOrder.verify(mHardware).getForwardedStats(ethernetIface); inOrder.verify(mHardware).stopOffloadControl(); @@ -551,7 +587,7 @@ public class OffloadControllerTest { OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue(); callback.onStoppedLimitReached(); - verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue()); + verify(mTetherStatsProviderCb, times(1)).onStatsUpdated(anyInt(), any(), any()); } @Test @@ -654,9 +690,10 @@ public class OffloadControllerTest { // Verify forwarded stats behaviour. verify(mHardware, times(1)).getForwardedStats(eq(RMNET0)); verify(mHardware, times(1)).getForwardedStats(eq(WLAN0)); + // TODO: verify the exact stats reported. + verify(mTetherStatsProviderCb, times(1)).onStatsUpdated(anyInt(), any(), any()); + verifyNoMoreInteractions(mTetherStatsProviderCb); verifyNoMoreInteractions(mHardware); - verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue()); - verifyNoMoreInteractions(mNMService); } @Test @@ -719,8 +756,8 @@ public class OffloadControllerTest { // Verify forwarded stats behaviour. verify(mHardware, times(1)).getForwardedStats(eq(RMNET0)); verify(mHardware, times(1)).getForwardedStats(eq(WLAN0)); - verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue()); - verifyNoMoreInteractions(mNMService); + verify(mTetherStatsProviderCb, times(1)).onStatsUpdated(anyInt(), any(), any()); + verifyNoMoreInteractions(mTetherStatsProviderCb); // TODO: verify local prefixes and downstreams are also pushed to the HAL. verify(mHardware, times(1)).setLocalPrefixes(mStringArrayCaptor.capture()); diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java index ef97ad418245..3635964dd6a6 100644 --- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java @@ -26,13 +26,6 @@ import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; -import static com.android.internal.R.array.config_mobile_hotspot_provision_app; -import static com.android.internal.R.array.config_tether_bluetooth_regexs; -import static com.android.internal.R.array.config_tether_dhcp_range; -import static com.android.internal.R.array.config_tether_upstream_types; -import static com.android.internal.R.array.config_tether_usb_regexs; -import static com.android.internal.R.array.config_tether_wifi_regexs; -import static com.android.networkstack.tethering.R.bool.config_tether_enable_legacy_dhcp_server; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -51,6 +44,7 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.util.test.BroadcastInterceptingContext; +import com.android.networkstack.tethering.R; import org.junit.After; import org.junit.Before; @@ -120,15 +114,18 @@ public class TetheringConfigurationTest { () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY), eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean())); - when(mResources.getStringArray(config_tether_dhcp_range)).thenReturn(new String[0]); - when(mResources.getStringArray(config_tether_usb_regexs)).thenReturn(new String[0]); - when(mResources.getStringArray(config_tether_wifi_regexs)) + when(mResources.getStringArray(R.array.config_tether_dhcp_range)).thenReturn( + new String[0]); + when(mResources.getStringArray(R.array.config_tether_usb_regexs)).thenReturn(new String[0]); + when(mResources.getStringArray(R.array.config_tether_wifi_regexs)) .thenReturn(new String[]{ "test_wlan\\d" }); - when(mResources.getStringArray(config_tether_bluetooth_regexs)).thenReturn(new String[0]); - when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(new int[0]); - when(mResources.getStringArray(config_mobile_hotspot_provision_app)) + when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs)).thenReturn( + new String[0]); + when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(new int[0]); + when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app)) .thenReturn(new String[0]); - when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false); + when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn( + false); mHasTelephonyManager = true; mMockContext = new MockContext(mContext); mEnableLegacyDhcpServer = false; @@ -140,7 +137,7 @@ public class TetheringConfigurationTest { } private TetheringConfiguration getTetheringConfiguration(int... legacyTetherUpstreamTypes) { - when(mResources.getIntArray(config_tether_upstream_types)).thenReturn( + when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn( legacyTetherUpstreamTypes); return new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); } @@ -224,7 +221,7 @@ public class TetheringConfigurationTest { @Test public void testNoDefinedUpstreamTypesAddsEthernet() { - when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(new int[]{}); + when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(new int[]{}); when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false); final TetheringConfiguration cfg = new TetheringConfiguration( @@ -246,7 +243,7 @@ public class TetheringConfigurationTest { @Test public void testDefinedUpstreamTypesSansEthernetAddsEthernet() { - when(mResources.getIntArray(config_tether_upstream_types)).thenReturn( + when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn( new int[]{TYPE_WIFI, TYPE_MOBILE_HIPRI}); when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false); @@ -264,7 +261,7 @@ public class TetheringConfigurationTest { @Test public void testDefinedUpstreamTypesWithEthernetDoesNotAddEthernet() { - when(mResources.getIntArray(config_tether_upstream_types)) + when(mResources.getIntArray(R.array.config_tether_upstream_types)) .thenReturn(new int[]{TYPE_WIFI, TYPE_ETHERNET, TYPE_MOBILE_HIPRI}); when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false); @@ -282,7 +279,8 @@ public class TetheringConfigurationTest { @Test public void testNewDhcpServerDisabled() { - when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(true); + when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn( + true); doReturn(false).when( () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY), eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean())); @@ -291,7 +289,8 @@ public class TetheringConfigurationTest { new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); assertTrue(enableByRes.enableLegacyDhcpServer); - when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false); + when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn( + false); doReturn(true).when( () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY), eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean())); @@ -303,7 +302,8 @@ public class TetheringConfigurationTest { @Test public void testNewDhcpServerEnabled() { - when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false); + when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn( + false); doReturn(false).when( () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY), eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean())); @@ -329,16 +329,17 @@ public class TetheringConfigurationTest { private void setUpResourceForSubId() { when(mResourcesForSubId.getStringArray( - config_tether_dhcp_range)).thenReturn(new String[0]); + R.array.config_tether_dhcp_range)).thenReturn(new String[0]); when(mResourcesForSubId.getStringArray( - config_tether_usb_regexs)).thenReturn(new String[0]); + R.array.config_tether_usb_regexs)).thenReturn(new String[0]); when(mResourcesForSubId.getStringArray( - config_tether_wifi_regexs)).thenReturn(new String[]{ "test_wlan\\d" }); + R.array.config_tether_wifi_regexs)).thenReturn(new String[]{ "test_wlan\\d" }); when(mResourcesForSubId.getStringArray( - config_tether_bluetooth_regexs)).thenReturn(new String[0]); - when(mResourcesForSubId.getIntArray(config_tether_upstream_types)).thenReturn(new int[0]); + R.array.config_tether_bluetooth_regexs)).thenReturn(new String[0]); + when(mResourcesForSubId.getIntArray(R.array.config_tether_upstream_types)).thenReturn( + new int[0]); when(mResourcesForSubId.getStringArray( - config_mobile_hotspot_provision_app)).thenReturn(PROVISIONING_APP_NAME); + R.array.config_mobile_hotspot_provision_app)).thenReturn(PROVISIONING_APP_NAME); } } diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java index e1fe3bfda4a8..59106240a08b 100644 --- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java @@ -19,6 +19,9 @@ package com.android.server.connectivity.tethering; import static android.hardware.usb.UsbManager.USB_CONFIGURED; import static android.hardware.usb.UsbManager.USB_CONNECTED; import static android.hardware.usb.UsbManager.USB_FUNCTION_RNDIS; +import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED; +import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED; +import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED; import static android.net.RouteInfo.RTN_UNICAST; import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED; import static android.net.TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY; @@ -37,8 +40,6 @@ import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED; import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; -import static com.android.networkstack.tethering.R.bool.config_tether_enable_legacy_dhcp_server; - import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -53,6 +54,7 @@ import static org.mockito.Mockito.any; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; @@ -60,6 +62,8 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import android.app.usage.NetworkStatsManager; +import android.bluetooth.BluetoothAdapter; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; @@ -68,9 +72,8 @@ import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.res.Resources; import android.hardware.usb.UsbManager; +import android.net.ConnectivityManager; import android.net.INetd; -import android.net.INetworkPolicyManager; -import android.net.INetworkStatsService; import android.net.ITetheringEventCallback; import android.net.InetAddresses; import android.net.InterfaceConfigurationParcel; @@ -99,7 +102,6 @@ import android.net.wifi.p2p.WifiP2pInfo; import android.net.wifi.p2p.WifiP2pManager; import android.os.Bundle; import android.os.Handler; -import android.os.INetworkManagementService; import android.os.Looper; import android.os.PersistableBundle; import android.os.RemoteException; @@ -120,6 +122,7 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.util.StateMachine; import com.android.internal.util.test.BroadcastInterceptingContext; import com.android.internal.util.test.FakeSettingsProvider; +import com.android.networkstack.tethering.R; import org.junit.After; import org.junit.Before; @@ -133,6 +136,7 @@ import java.net.Inet4Address; import java.net.Inet6Address; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Vector; @RunWith(AndroidJUnit4.class) @@ -151,9 +155,7 @@ public class TetheringTest { @Mock private ApplicationInfo mApplicationInfo; @Mock private Context mContext; - @Mock private INetworkManagementService mNMService; - @Mock private INetworkStatsService mStatsService; - @Mock private INetworkPolicyManager mPolicyManager; + @Mock private NetworkStatsManager mStatsManager; @Mock private OffloadHardwareInterface mOffloadHardwareInterface; @Mock private Resources mResources; @Mock private TelephonyManager mTelephonyManager; @@ -167,6 +169,7 @@ public class TetheringTest { @Mock private INetd mNetd; @Mock private UserManager mUserManager; @Mock private NetworkRequest mNetworkRequest; + @Mock private ConnectivityManager mCm; private final MockIpServerDependencies mIpServerDependencies = spy(new MockIpServerDependencies()); @@ -217,6 +220,8 @@ public class TetheringTest { if (Context.USB_SERVICE.equals(name)) return mUsbManager; if (Context.TELEPHONY_SERVICE.equals(name)) return mTelephonyManager; if (Context.USER_SERVICE.equals(name)) return mUserManager; + if (Context.NETWORK_STATS_SERVICE.equals(name)) return mStatsManager; + if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm; return super.getSystemService(name); } @@ -334,21 +339,6 @@ public class TetheringTest { } @Override - public INetworkManagementService getINetworkManagementService() { - return mNMService; - } - - @Override - public INetworkStatsService getINetworkStatsService() { - return mStatsService; - } - - @Override - public INetworkPolicyManager getINetworkPolicyManager() { - return mPolicyManager; - } - - @Override public INetd getINetd(Context context) { return mNetd; } @@ -362,6 +352,12 @@ public class TetheringTest { public Context getContext() { return mServiceContext; } + + @Override + public BluetoothAdapter getBluetoothAdapter() { + // TODO: add test for bluetooth tethering. + return null; + } } private static UpstreamNetworkState buildMobileUpstreamState(boolean withIPv4, @@ -420,24 +416,24 @@ public class TetheringTest { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - when(mResources.getStringArray(com.android.internal.R.array.config_tether_dhcp_range)) + when(mResources.getStringArray(R.array.config_tether_dhcp_range)) .thenReturn(new String[0]); - when(mResources.getStringArray(com.android.internal.R.array.config_tether_usb_regexs)) + when(mResources.getStringArray(R.array.config_tether_usb_regexs)) .thenReturn(new String[] { "test_rndis\\d" }); - when(mResources.getStringArray(com.android.internal.R.array.config_tether_wifi_regexs)) + when(mResources.getStringArray(R.array.config_tether_wifi_regexs)) .thenReturn(new String[]{ "test_wlan\\d" }); - when(mResources.getStringArray(com.android.internal.R.array.config_tether_wifi_p2p_regexs)) + when(mResources.getStringArray(R.array.config_tether_wifi_p2p_regexs)) .thenReturn(new String[]{ "test_p2p-p2p\\d-.*" }); - when(mResources.getStringArray(com.android.internal.R.array.config_tether_bluetooth_regexs)) + when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs)) .thenReturn(new String[0]); - when(mResources.getIntArray(com.android.internal.R.array.config_tether_upstream_types)) - .thenReturn(new int[0]); - when(mResources.getBoolean(com.android.internal.R.bool.config_tether_upstream_automatic)) - .thenReturn(false); - when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false); + when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(new int[0]); + when(mResources.getBoolean(R.bool.config_tether_upstream_automatic)).thenReturn(false); + when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn( + false); when(mNetd.interfaceGetList()) .thenReturn(new String[] { TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_USB_IFNAME, TEST_P2P_IFNAME}); + when(mResources.getString(R.string.config_wifi_tether_enable)).thenReturn(""); mInterfaceConfiguration = new InterfaceConfigurationParcel(); mInterfaceConfiguration.flags = new String[0]; when(mRouterAdvertisementDaemon.start()) @@ -457,7 +453,7 @@ public class TetheringTest { mServiceContext.registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION_TETHER_STATE_CHANGED)); mTethering = makeTethering(); - verify(mNMService).registerTetheringStatsProvider(any(), anyString()); + verify(mStatsManager, times(1)).registerNetworkStatsProvider(anyString(), any()); verify(mNetd).registerUnsolicitedEventListener(any()); final ArgumentCaptor<PhoneStateListener> phoneListenerCaptor = ArgumentCaptor.forClass(PhoneStateListener.class); @@ -501,13 +497,15 @@ public class TetheringTest { p2pInfo.groupFormed = isGroupFormed; p2pInfo.isGroupOwner = isGroupOwner; - WifiP2pGroup group = new WifiP2pGroup(); - group.setIsGroupOwner(isGroupOwner); - group.setInterface(ifname); + WifiP2pGroup group = mock(WifiP2pGroup.class); + when(group.isGroupOwner()).thenReturn(isGroupOwner); + when(group.getInterface()).thenReturn(ifname); + + final Intent intent = mock(Intent.class); + when(intent.getAction()).thenReturn(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); + when(intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO)).thenReturn(p2pInfo); + when(intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP)).thenReturn(group); - final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); - intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO, p2pInfo); - intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP, group); mServiceContext.sendBroadcastAsUserMultiplePermissions(intent, UserHandle.ALL, P2P_RECEIVER_PERMISSIONS_FOR_BROADCAST); } @@ -698,7 +696,8 @@ public class TetheringTest { @Test public void workingMobileUsbTethering_IPv4LegacyDhcp() { - when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(true); + when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn( + true); sendConfigurationChanged(); final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState(); runUsbTethering(upstreamState); @@ -786,8 +785,7 @@ public class TetheringTest { @Test public void configTetherUpstreamAutomaticIgnoresConfigTetherUpstreamTypes() throws Exception { - when(mResources.getBoolean(com.android.internal.R.bool.config_tether_upstream_automatic)) - .thenReturn(true); + when(mResources.getBoolean(R.bool.config_tether_upstream_automatic)).thenReturn(true); sendConfigurationChanged(); // Setup IPv6 @@ -1315,7 +1313,7 @@ public class TetheringTest { private void workingWifiP2pGroupOwnerLegacyMode( boolean emulateInterfaceStatusChanged) throws Exception { // change to legacy mode and update tethering information by chaning SIM - when(mResources.getStringArray(com.android.internal.R.array.config_tether_wifi_p2p_regexs)) + when(mResources.getStringArray(R.array.config_tether_wifi_p2p_regexs)) .thenReturn(new String[]{}); final int fakeSubId = 1234; mPhoneStateListener.onActiveDataSubscriptionIdChanged(fakeSubId); @@ -1353,6 +1351,50 @@ public class TetheringTest { workingWifiP2pGroupClient(false); } + private void setDataSaverEnabled(boolean enabled) { + final Intent intent = new Intent(ACTION_RESTRICT_BACKGROUND_CHANGED); + mServiceContext.sendBroadcastAsUser(intent, UserHandle.ALL); + + final int status = enabled ? RESTRICT_BACKGROUND_STATUS_ENABLED + : RESTRICT_BACKGROUND_STATUS_DISABLED; + when(mCm.getRestrictBackgroundStatus()).thenReturn(status); + mLooper.dispatchAll(); + } + + @Test + public void testDataSaverChanged() { + // Start Tethering. + final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState(); + runUsbTethering(upstreamState); + assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_USB_IFNAME); + // Data saver is ON. + setDataSaverEnabled(true); + // Verify that tethering should be disabled. + verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_NONE); + mTethering.interfaceRemoved(TEST_USB_IFNAME); + mLooper.dispatchAll(); + assertEquals(mTethering.getTetheredIfaces(), new String[0]); + reset(mUsbManager); + + runUsbTethering(upstreamState); + // Verify that user can start tethering again without turning OFF data saver. + assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_USB_IFNAME); + + // If data saver is keep ON with change event, tethering should not be OFF this time. + setDataSaverEnabled(true); + verify(mUsbManager, times(0)).setCurrentFunctions(UsbManager.FUNCTION_NONE); + assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_USB_IFNAME); + + // If data saver is turned OFF, it should not change tethering. + setDataSaverEnabled(false); + verify(mUsbManager, times(0)).setCurrentFunctions(UsbManager.FUNCTION_NONE); + assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_USB_IFNAME); + } + + private static <T> void assertContains(Collection<T> collection, T element) { + assertTrue(element + " not found in " + collection, collection.contains(element)); + } + // TODO: Test that a request for hotspot mode doesn't interfere with an // already operating tethering mode interface. } diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto index 068707fee173..21100458adc1 100644 --- a/proto/src/system_messages.proto +++ b/proto/src/system_messages.proto @@ -237,6 +237,9 @@ message SystemMessage { // Inform the user that the current network may not support using a randomized MAC address. NOTE_NETWORK_NO_MAC_RANDOMIZATION_SUPPORT = 56; + // Inform the user that EAP failure occurs + NOTE_WIFI_EAP_FAILURE = 57; + // ADD_NEW_IDS_ABOVE_THIS_LINE // Legacy IDs with arbitrary values appear below // Legacy IDs existed as stable non-conflicting constants prior to the O release diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 5eaa80a5143b..c3965c44d4c0 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -36,8 +36,12 @@ import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; +import android.graphics.Bitmap; +import android.graphics.Point; +import android.graphics.Rect; import android.graphics.Region; import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManagerGlobal; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -54,6 +58,7 @@ import android.util.SparseArray; import android.view.Display; import android.view.KeyEvent; import android.view.MagnificationSpec; +import android.view.SurfaceControl; import android.view.View; import android.view.WindowInfo; import android.view.accessibility.AccessibilityCache; @@ -90,6 +95,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ private static final String LOG_TAG = "AbstractAccessibilityServiceConnection"; private static final int WAIT_WINDOWS_TIMEOUT_MILLIS = 5000; + protected static final String TAKE_SCREENSHOT = "takeScreenshot"; protected final Context mContext; protected final SystemSupport mSystemSupport; protected final WindowManagerInternal mWindowManagerService; @@ -934,6 +940,54 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ mInvocationHandler.setSoftKeyboardCallbackEnabled(enabled); } + @Nullable + @Override + public Bitmap takeScreenshot(int displayId) { + synchronized (mLock) { + if (!hasRightsToCurrentUserLocked()) { + return null; + } + + if (!mSecurityPolicy.canTakeScreenshotLocked(this)) { + return null; + } + } + + if (!mSecurityPolicy.checkAccessibilityAccess(this)) { + return null; + } + + final Display display = DisplayManagerGlobal.getInstance() + .getRealDisplay(displayId); + if (display == null) { + return null; + } + final Point displaySize = new Point(); + display.getRealSize(displaySize); + + final int rotation = display.getRotation(); + Bitmap screenShot = null; + + final long identity = Binder.clearCallingIdentity(); + try { + final Rect crop = new Rect(0, 0, displaySize.x, displaySize.y); + // TODO (b/145893483): calling new API with the display as a parameter + // when surface control supported. + screenShot = SurfaceControl.screenshot(crop, displaySize.x, displaySize.y, + rotation); + if (screenShot != null) { + // Optimization for telling the bitmap that all of the pixels are known to be + // opaque (false). This is meant as a drawing hint, as in some cases a bitmap + // that is known to be opaque can take a faster drawing case than one that may + // have non-opaque per-pixel alpha values. + screenShot.setHasAlpha(false); + } + } finally { + Binder.restoreCallingIdentity(identity); + } + return screenShot; + } + @Override public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return; @@ -1018,6 +1072,20 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + /** + * Gets windowId of given token. + * + * @param token The token + * @return window id + */ + @Override + public int getWindowIdForLeashToken(@NonNull IBinder token) { + synchronized (mLock) { + // TODO: Add a method to lookup window ID by given leash token. + return -1; + } + } + public void resetLocked() { mSystemSupport.getKeyEventDispatcher().flush(this); try { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 58d3489a8cc8..c9fdd5ae407a 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -411,6 +411,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (reboundAService) { onUserStateChangedLocked(userState); } + migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, packageName); } } @@ -811,9 +812,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub userState.setTouchExplorationEnabledLocked(touchExplorationEnabled); userState.setDisplayMagnificationEnabledLocked(false); - userState.setNavBarMagnificationEnabledLocked(false); userState.disableShortcutMagnificationLocked(); - userState.setAutoclickEnabledLocked(false); userState.mEnabledServices.clear(); userState.mEnabledServices.add(service); @@ -853,17 +852,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub * navigation area has been clicked. * * @param displayId The logical display id. + * @param targetName The flattened {@link ComponentName} string or the class name of a system + * class implementing a supported accessibility feature, or {@code null} if there's no + * specified target. */ @Override - public void notifyAccessibilityButtonClicked(int displayId) { + public void notifyAccessibilityButtonClicked(int displayId, String targetName) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Caller does not hold permission " + android.Manifest.permission.STATUS_BAR_SERVICE); } - synchronized (mLock) { - notifyAccessibilityButtonClickedLocked(displayId); - } + mMainHandler.sendMessage(obtainMessage( + AccessibilityManagerService::performAccessibilityShortcutInternal, this, + displayId, ACCESSIBILITY_BUTTON, targetName)); } /** @@ -1023,6 +1025,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // the state since the context in which the current user // state was used has changed since it was inactive. onUserStateChangedLocked(userState); + migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, null); if (announceNewUser) { // Schedule announcement of the current user if needed. @@ -1132,66 +1135,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - // TODO(a11y shortcut): Remove this function and Use #performAccessibilityShortcutInternal( - // ACCESSIBILITY_BUTTON) instead, after the new Settings shortcut Ui merged. - private void notifyAccessibilityButtonClickedLocked(int displayId) { - final AccessibilityUserState state = getCurrentUserStateLocked(); - - int potentialTargets = state.isNavBarMagnificationEnabledLocked() ? 1 : 0; - for (int i = state.mBoundServices.size() - 1; i >= 0; i--) { - final AccessibilityServiceConnection service = state.mBoundServices.get(i); - if (service.mRequestAccessibilityButton) { - potentialTargets++; - } - } - - if (potentialTargets == 0) { - return; - } - if (potentialTargets == 1) { - if (state.isNavBarMagnificationEnabledLocked()) { - mMainHandler.sendMessage(obtainMessage( - AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this, - displayId)); - return; - } else { - for (int i = state.mBoundServices.size() - 1; i >= 0; i--) { - final AccessibilityServiceConnection service = state.mBoundServices.get(i); - if (service.mRequestAccessibilityButton) { - service.notifyAccessibilityButtonClickedLocked(displayId); - return; - } - } - } - } else { - if (state.getServiceAssignedToAccessibilityButtonLocked() == null - && !state.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) { - mMainHandler.sendMessage(obtainMessage( - AccessibilityManagerService::showAccessibilityTargetsSelection, this, - displayId, ACCESSIBILITY_BUTTON)); - } else if (state.isNavBarMagnificationEnabledLocked() - && state.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) { - mMainHandler.sendMessage(obtainMessage( - AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this, - displayId)); - return; - } else { - for (int i = state.mBoundServices.size() - 1; i >= 0; i--) { - final AccessibilityServiceConnection service = state.mBoundServices.get(i); - if (service.mRequestAccessibilityButton && (service.mComponentName.equals( - state.getServiceAssignedToAccessibilityButtonLocked()))) { - service.notifyAccessibilityButtonClickedLocked(displayId); - return; - } - } - } - // The user may have turned off the assigned service or feature - mMainHandler.sendMessage(obtainMessage( - AccessibilityManagerService::showAccessibilityTargetsSelection, this, - displayId, ACCESSIBILITY_BUTTON)); - } - } - private void sendAccessibilityButtonToInputFilter(int displayId) { synchronized (mLock) { if (mHasInputFilter && mInputFilter != null) { @@ -1204,8 +1147,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @ShortcutType int shortcutType) { Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + intent.putExtra(AccessibilityManager.EXTRA_SHORTCUT_TYPE, shortcutType); final Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle(); - bundle.putInt(AccessibilityManager.EXTRA_SHORTCUT_TYPE, shortcutType); mContext.startActivityAsUser(intent, bundle, UserHandle.of(mCurrentUserId)); } @@ -1635,8 +1578,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (userState.isDisplayMagnificationEnabledLocked()) { flags |= AccessibilityInputFilter.FLAG_FEATURE_SCREEN_MAGNIFIER; } - if (userState.isNavBarMagnificationEnabledLocked() - || userState.isShortcutKeyMagnificationEnabledLocked()) { + if (userState.isShortcutMagnificationEnabledLocked()) { flags |= AccessibilityInputFilter.FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER; } if (userHasMagnificationServicesLocked(userState)) { @@ -1886,14 +1828,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, 0, userState.mUserId) == 1; - final boolean navBarMagnificationEnabled = Settings.Secure.getIntForUser( - mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, - 0, userState.mUserId) == 1; - if ((displayMagnificationEnabled != userState.isDisplayMagnificationEnabledLocked()) - || (navBarMagnificationEnabled != userState.isNavBarMagnificationEnabledLocked())) { + if ((displayMagnificationEnabled != userState.isDisplayMagnificationEnabledLocked())) { userState.setDisplayMagnificationEnabledLocked(displayMagnificationEnabled); - userState.setNavBarMagnificationEnabledLocked(navBarMagnificationEnabled); return true; } return false; @@ -2088,8 +2024,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // displays in one display. It's not a real display and there's no input events for it. final ArrayList<Display> displays = getValidDisplayList(); if (userState.isDisplayMagnificationEnabledLocked() - || userState.isNavBarMagnificationEnabledLocked() - || userState.isShortcutKeyMagnificationEnabledLocked()) { + || userState.isShortcutMagnificationEnabledLocked()) { for (int i = 0; i < displays.size(); i++) { final Display display = displays.get(i); getMagnificationController().register(display.getDisplayId()); @@ -2208,6 +2143,85 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub scheduleNotifyClientsOfServicesStateChangeLocked(userState); } + /** + * 1) Check if the service assigned to accessibility button target sdk version > Q. + * If it isn't, remove it from the list and associated setting. + * (It happens when an accessibility service package is downgraded.) + * 2) Check if an enabled service targeting sdk version > Q and requesting a11y button is + * assigned to a shortcut. If it isn't, assigns it to the accessibility button. + * (It happens when an enabled accessibility service package is upgraded.) + * + * @param packageName The package name to check, or {@code null} to check all services. + */ + private void migrateAccessibilityButtonSettingsIfNecessaryLocked( + AccessibilityUserState userState, @Nullable String packageName) { + final Set<String> buttonTargets = + userState.getShortcutTargetsLocked(ACCESSIBILITY_BUTTON); + int lastSize = buttonTargets.size(); + buttonTargets.removeIf(name -> { + if (packageName != null && name != null && !name.contains(packageName)) { + return false; + } + final ComponentName componentName = ComponentName.unflattenFromString(name); + if (componentName == null) { + return false; + } + final AccessibilityServiceInfo serviceInfo = + userState.getInstalledServiceInfoLocked(componentName); + if (serviceInfo == null) { + return false; + } + if (serviceInfo.getResolveInfo().serviceInfo.applicationInfo + .targetSdkVersion > Build.VERSION_CODES.Q) { + return false; + } + // A11y services targeting sdk version <= Q should not be in the list. + return true; + }); + boolean changed = (lastSize != buttonTargets.size()); + lastSize = buttonTargets.size(); + + final Set<String> shortcutKeyTargets = + userState.getShortcutTargetsLocked(ACCESSIBILITY_SHORTCUT_KEY); + userState.mEnabledServices.forEach(componentName -> { + if (packageName != null && componentName != null + && !packageName.equals(componentName.getPackageName())) { + return; + } + final AccessibilityServiceInfo serviceInfo = + userState.getInstalledServiceInfoLocked(componentName); + if (serviceInfo == null) { + return; + } + final boolean requestA11yButton = (serviceInfo.flags + & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0; + if (!(serviceInfo.getResolveInfo().serviceInfo.applicationInfo + .targetSdkVersion > Build.VERSION_CODES.Q && requestA11yButton)) { + return; + } + final String serviceName = serviceInfo.getComponentName().flattenToString(); + if (TextUtils.isEmpty(serviceName)) { + return; + } + if (shortcutKeyTargets.contains(serviceName) || buttonTargets.contains(serviceName)) { + return; + } + // For enabled a11y services targeting sdk version > Q and requesting a11y button should + // be assigned to a shortcut. + buttonTargets.add(serviceName); + }); + changed |= (lastSize != buttonTargets.size()); + if (!changed) { + return; + } + + // Update setting key with new value. + persistColonDelimitedSetToSettingLocked( + Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT, + userState.mUserId, buttonTargets, str -> str); + scheduleNotifyClientsOfServicesStateChangeLocked(userState); + } + private void updateRecommendedUiTimeoutLocked(AccessibilityUserState userState) { int newNonInteractiveUiTimeout = userState.getUserNonInteractiveUiTimeoutLocked(); int newInteractiveUiTimeout = userState.getUserInteractiveUiTimeoutLocked(); @@ -2269,9 +2283,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub * AIDL-exposed method to be called when the accessibility shortcut key is enabled. Requires * permission to write secure settings, since someone with that permission can enable * accessibility services themselves. + * + * @param targetName The flattened {@link ComponentName} string or the class name of a system + * class implementing a supported accessibility feature, or {@code null} if there's no + * specified target. */ @Override - public void performAccessibilityShortcut() { + public void performAccessibilityShortcut(String targetName) { if ((UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) && (mContext.checkCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY) != PackageManager.PERMISSION_GRANTED)) { @@ -2280,7 +2298,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } mMainHandler.sendMessage(obtainMessage( AccessibilityManagerService::performAccessibilityShortcutInternal, this, - Display.DEFAULT_DISPLAY, ACCESSIBILITY_SHORTCUT_KEY)); + Display.DEFAULT_DISPLAY, ACCESSIBILITY_SHORTCUT_KEY, targetName)); } /** @@ -2288,20 +2306,31 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub * * @param shortcutType The shortcut type. * @param displayId The display id of the accessibility button. + * @param targetName The flattened {@link ComponentName} string or the class name of a system + * class implementing a supported accessibility feature, or {@code null} if there's no + * specified target. */ private void performAccessibilityShortcutInternal(int displayId, - @ShortcutType int shortcutType) { + @ShortcutType int shortcutType, @Nullable String targetName) { final List<String> shortcutTargets = getAccessibilityShortcutTargetsInternal(shortcutType); if (shortcutTargets.isEmpty()) { Slog.d(LOG_TAG, "No target to perform shortcut, shortcutType=" + shortcutType); return; } - // In case there are many targets assigned to the given shortcut. - if (shortcutTargets.size() > 1) { - showAccessibilityTargetsSelection(displayId, shortcutType); - return; + // In case the caller specified a target name + if (targetName != null) { + if (!shortcutTargets.contains(targetName)) { + Slog.d(LOG_TAG, "Perform shortcut failed, invalid target name:" + targetName); + return; + } + } else { + // In case there are many targets assigned to the given shortcut. + if (shortcutTargets.size() > 1) { + showAccessibilityTargetsSelection(displayId, shortcutType); + return; + } + targetName = shortcutTargets.get(0); } - final String targetName = shortcutTargets.get(0); // In case user assigned magnification to the given shortcut. if (targetName.equals(MAGNIFICATION_CONTROLLER_NAME)) { sendAccessibilityButtonToInputFilter(displayId); @@ -2844,11 +2873,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private final Uri mDisplayMagnificationEnabledUri = Settings.Secure.getUriFor( Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED); - // TODO(a11y shortcut): Remove this setting key, and have a migrate function in - // Setting provider after new shortcut UI merged. - private final Uri mNavBarMagnificationEnabledUri = Settings.Secure.getUriFor( - Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED); - private final Uri mAutoclickEnabledUri = Settings.Secure.getUriFor( Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED); @@ -2888,8 +2912,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub false, this, UserHandle.USER_ALL); contentResolver.registerContentObserver(mDisplayMagnificationEnabledUri, false, this, UserHandle.USER_ALL); - contentResolver.registerContentObserver(mNavBarMagnificationEnabledUri, - false, this, UserHandle.USER_ALL); contentResolver.registerContentObserver(mAutoclickEnabledUri, false, this, UserHandle.USER_ALL); contentResolver.registerContentObserver(mEnabledAccessibilityServicesUri, @@ -2924,8 +2946,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (readTouchExplorationEnabledSettingLocked(userState)) { onUserStateChangedLocked(userState); } - } else if (mDisplayMagnificationEnabledUri.equals(uri) - || mNavBarMagnificationEnabledUri.equals(uri)) { + } else if (mDisplayMagnificationEnabledUri.equals(uri)) { if (readMagnificationEnabledSettingsLocked(userState)) { onUserStateChangedLocked(userState); } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java index 203210998c48..7dbec7c8c0c5 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java @@ -326,6 +326,19 @@ public class AccessibilitySecurityPolicy { } /** + * Checks if a service can take screenshot. + * + * @param service The service requesting access + * + * @return Whether ot not the service may take screenshot + */ + public boolean canTakeScreenshotLocked( + @NonNull AbstractAccessibilityServiceConnection service) { + return (service.getCapabilities() + & AccessibilityServiceInfo.CAPABILITY_CAN_TAKE_SCREENSHOT) != 0; + } + + /** * Returns the parent userId of the profile according to the specified userId. * * @param userId The userId to check @@ -426,6 +439,7 @@ public class AccessibilitySecurityPolicy { return false; } } + // TODO: Check parent windowId if the giving windowId is from embedded view hierarchy. if (windowId == mAccessibilityWindowManager.getActiveWindowId(userId)) { return true; } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java index 37ac3ec6f105..25911a7ed0ea 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java @@ -16,6 +16,8 @@ package com.android.server.accessibility; +import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT; + import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import android.Manifest; @@ -25,16 +27,20 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ParceledListSlice; +import android.graphics.Bitmap; import android.os.Binder; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Process; +import android.os.RemoteCallback; import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; import android.util.Slog; import android.view.Display; +import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; @@ -313,48 +319,16 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect } } - // TODO(a11y shortcut): Refactoring the logic here, after the new Settings shortcut Ui merged. public boolean isAccessibilityButtonAvailableLocked(AccessibilityUserState userState) { // If the service does not request the accessibility button, it isn't available if (!mRequestAccessibilityButton) { return false; } - // If the accessibility button isn't currently shown, it cannot be available to services if (!mSystemSupport.isAccessibilityButtonShown()) { return false; } - - // If magnification is on and assigned to the accessibility button, services cannot be - if (userState.isNavBarMagnificationEnabledLocked() - && userState.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) { - return false; - } - - int requestingServices = 0; - for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) { - final AccessibilityServiceConnection service = userState.mBoundServices.get(i); - if (service.mRequestAccessibilityButton) { - requestingServices++; - } - } - - if (requestingServices == 1) { - // If only a single service is requesting, it must be this service, and the - // accessibility button is available to it - return true; - } else { - // With more than one active service, we derive the target from the user's settings - if (userState.getServiceAssignedToAccessibilityButtonLocked() == null) { - // If the user has not made an assignment, we treat the button as available to - // all services until the user interacts with the button to make an assignment - return true; - } else { - // If an assignment was made, it defines availability - return mComponentName.equals( - userState.getServiceAssignedToAccessibilityButtonLocked()); - } - } + return true; } @Override @@ -419,4 +393,15 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect } } } + + @Override + public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) { + mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> { + final Bitmap screenshot = super.takeScreenshot(displayId); + // Send back the result. + final Bundle payload = new Bundle(); + payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT, screenshot); + callback.sendResult(payload); + }, null).recycleOnUse()); + } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java index a163f7434e1f..ebe2af62b5db 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java @@ -100,7 +100,6 @@ class AccessibilityUserState { private boolean mIsAutoclickEnabled; private boolean mIsDisplayMagnificationEnabled; private boolean mIsFilterKeyEventsEnabled; - private boolean mIsNavBarMagnificationEnabled; private boolean mIsPerformGesturesEnabled; private boolean mIsTextHighContrastEnabled; private boolean mIsTouchExplorationEnabled; @@ -153,7 +152,6 @@ class AccessibilityUserState { mAccessibilityButtonTargets.clear(); mIsTouchExplorationEnabled = false; mIsDisplayMagnificationEnabled = false; - mIsNavBarMagnificationEnabled = false; mIsAutoclickEnabled = false; mUserNonInteractiveUiTimeout = 0; mUserInteractiveUiTimeout = 0; @@ -435,8 +433,6 @@ class AccessibilityUserState { pw.append(", touchExplorationEnabled=").append(String.valueOf(mIsTouchExplorationEnabled)); pw.append(", displayMagnificationEnabled=").append(String.valueOf( mIsDisplayMagnificationEnabled)); - pw.append(", navBarMagnificationEnabled=").append(String.valueOf( - mIsNavBarMagnificationEnabled)); pw.append(", autoclickEnabled=").append(String.valueOf(mIsAutoclickEnabled)); pw.append(", nonInteractiveUiTimeout=").append(String.valueOf(mNonInteractiveUiTimeout)); pw.append(", interactiveUiTimeout=").append(String.valueOf(mInteractiveUiTimeout)); @@ -553,8 +549,12 @@ class AccessibilityUserState { mLastSentClientState = state; } - public boolean isShortcutKeyMagnificationEnabledLocked() { - return mAccessibilityShortcutKeyTargets.contains(MAGNIFICATION_CONTROLLER_NAME); + /** + * Returns true if navibar magnification or shortcut key magnification is enabled. + */ + public boolean isShortcutMagnificationEnabledLocked() { + return mAccessibilityShortcutKeyTargets.contains(MAGNIFICATION_CONTROLLER_NAME) + || mAccessibilityButtonTargets.contains(MAGNIFICATION_CONTROLLER_NAME); } /** @@ -690,28 +690,4 @@ class AccessibilityUserState { public void setUserNonInteractiveUiTimeoutLocked(int timeout) { mUserNonInteractiveUiTimeout = timeout; } - - // TODO(a11y shortcut): These functions aren't necessary, after the new Settings shortcut Ui - // is merged. - boolean isNavBarMagnificationEnabledLocked() { - return mIsNavBarMagnificationEnabled; - } - - void setNavBarMagnificationEnabledLocked(boolean enabled) { - mIsNavBarMagnificationEnabled = enabled; - } - - boolean isNavBarMagnificationAssignedToAccessibilityButtonLocked() { - return mAccessibilityButtonTargets.contains(MAGNIFICATION_CONTROLLER_NAME); - } - - ComponentName getServiceAssignedToAccessibilityButtonLocked() { - final String targetName = mAccessibilityButtonTargets.isEmpty() ? null - : mAccessibilityButtonTargets.valueAt(0); - if (targetName == null) { - return null; - } - return ComponentName.unflattenFromString(targetName); - } - // TODO(a11y shortcut): End } diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java index 7a8a112fae40..5d9af26a8339 100644 --- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java @@ -25,6 +25,7 @@ import android.content.Context; import android.os.Handler; import android.os.IBinder; import android.os.IBinder.DeathRecipient; +import android.os.RemoteCallback; import android.os.RemoteException; import android.util.Slog; import android.view.Display; @@ -325,5 +326,8 @@ class UiAutomationManager { @Override public void onFingerprintGesture(int gesture) {} + + @Override + public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {} } } diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java index 2891c6c294f5..4c9e590592fe 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java @@ -82,7 +82,7 @@ class MultiTap extends GestureMatcher { mCurrentTaps++; if (mCurrentTaps == mTargetTaps) { // Done. - completeAfterTapTimeout(event, rawEvent, policyFlags); + completeGesture(event, rawEvent, policyFlags); return; } // Needs more taps. diff --git a/services/api/current.txt b/services/api/current.txt index d802177e249b..18e38b1c6547 100644 --- a/services/api/current.txt +++ b/services/api/current.txt @@ -1 +1,31 @@ // Signature format: 2.0 +package com.android.server { + + public abstract class SystemService { + ctor public SystemService(@NonNull android.content.Context); + method @NonNull public final android.content.Context getContext(); + method public boolean isSupportedUser(@NonNull com.android.server.SystemService.TargetUser); + method public void onBootPhase(int); + method public void onCleanupUser(@NonNull com.android.server.SystemService.TargetUser); + method public abstract void onStart(); + method public void onStartUser(@NonNull com.android.server.SystemService.TargetUser); + method public void onStopUser(@NonNull com.android.server.SystemService.TargetUser); + method public void onSwitchUser(@Nullable com.android.server.SystemService.TargetUser, @NonNull com.android.server.SystemService.TargetUser); + method public void onUnlockUser(@NonNull com.android.server.SystemService.TargetUser); + method protected final void publishBinderService(@NonNull String, @NonNull android.os.IBinder); + method protected final void publishBinderService(@NonNull String, @NonNull android.os.IBinder, boolean); + field public static final int PHASE_ACTIVITY_MANAGER_READY = 550; // 0x226 + field public static final int PHASE_BOOT_COMPLETED = 1000; // 0x3e8 + field public static final int PHASE_DEVICE_SPECIFIC_SERVICES_READY = 520; // 0x208 + field public static final int PHASE_LOCK_SETTINGS_READY = 480; // 0x1e0 + field public static final int PHASE_SYSTEM_SERVICES_READY = 500; // 0x1f4 + field public static final int PHASE_THIRD_PARTY_APPS_CAN_START = 600; // 0x258 + field public static final int PHASE_WAIT_FOR_DEFAULT_DISPLAY = 100; // 0x64 + } + + public static final class SystemService.TargetUser { + method @NonNull public android.os.UserHandle getUserHandle(); + } + +} + diff --git a/services/art-profile b/services/art-profile index a0338d55c55f..4e113c818c6c 100644 --- a/services/art-profile +++ b/services/art-profile @@ -3066,18 +3066,18 @@ HSPLcom/android/server/am/ActivityManagerShellCommand;->runSendBroadcast(Ljava/i HSPLcom/android/server/am/AppBindRecord;->dumpInIntentBind(Ljava/io/PrintWriter;Ljava/lang/String;)V PLcom/android/server/am/AppBindRecord;->toString()Ljava/lang/String; PLcom/android/server/am/AppBindRecord;->writeToProto(Landroid/util/proto/ProtoOutputStream;J)V -PLcom/android/server/am/AppCompactor$1;->onPropertyChanged(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V -HSPLcom/android/server/am/AppCompactor$MemCompactionHandler;->handleMessage(Landroid/os/Message;)V -HSPLcom/android/server/am/AppCompactor;-><init>(Lcom/android/server/am/ActivityManagerService;)V -HSPLcom/android/server/am/AppCompactor;->access$1000(Lcom/android/server/am/AppCompactor;)V -HSPLcom/android/server/am/AppCompactor;->access$700(Lcom/android/server/am/AppCompactor;)Lcom/android/server/am/ActivityManagerService; -HSPLcom/android/server/am/AppCompactor;->access$800(Lcom/android/server/am/AppCompactor;)Ljava/util/ArrayList; -HSPLcom/android/server/am/AppCompactor;->access$900(Lcom/android/server/am/AppCompactor;)Ljava/util/Random; -PLcom/android/server/am/AppCompactor;->dump(Ljava/io/PrintWriter;)V -HSPLcom/android/server/am/AppCompactor;->init()V -HSPLcom/android/server/am/AppCompactor;->updateCompactionThrottles()V -HSPLcom/android/server/am/AppCompactor;->updateUseCompaction()V -HSPLcom/android/server/am/AppCompactor;->useCompaction()Z +PLcom/android/server/am/CachedAppOptimizer$1;->onPropertyChanged(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V +HSPLcom/android/server/am/CachedAppOptimizer$MemCompactionHandler;->handleMessage(Landroid/os/Message;)V +HSPLcom/android/server/am/CachedAppOptimizer;-><init>(Lcom/android/server/am/ActivityManagerService;)V +HSPLcom/android/server/am/CachedAppOptimizer;->access$1000(Lcom/android/server/am/CachedAppOptimizer;)V +HSPLcom/android/server/am/CachedAppOptimizer;->access$700(Lcom/android/server/am/CachedAppOptimizer;)Lcom/android/server/am/ActivityManagerService; +HSPLcom/android/server/am/CachedAppOptimizer;->access$800(Lcom/android/server/am/CachedAppOptimizer;)Ljava/util/ArrayList; +HSPLcom/android/server/am/CachedAppOptimizer;->access$900(Lcom/android/server/am/CachedAppOptimizer;)Ljava/util/Random; +PLcom/android/server/am/CachedAppOptimizer;->dump(Ljava/io/PrintWriter;)V +HSPLcom/android/server/am/CachedAppOptimizer;->init()V +HSPLcom/android/server/am/CachedAppOptimizer;->updateCompactionThrottles()V +HSPLcom/android/server/am/CachedAppOptimizer;->updateUseCompaction()V +HSPLcom/android/server/am/CachedAppOptimizer;->useCompaction()Z PLcom/android/server/am/AppErrorDialog$1;->handleMessage(Landroid/os/Message;)V PLcom/android/server/am/AppErrorDialog;-><init>(Landroid/content/Context;Lcom/android/server/am/ActivityManagerService;Lcom/android/server/am/AppErrorDialog$Data;)V PLcom/android/server/am/AppErrorDialog;->onClick(Landroid/view/View;)V @@ -18632,9 +18632,9 @@ Lcom/android/server/am/ActivityManagerService$UiHandler; Lcom/android/server/am/ActivityManagerService$UidObserverRegistration; Lcom/android/server/am/ActivityManagerService; Lcom/android/server/am/AppBindRecord; -Lcom/android/server/am/AppCompactor$1; -Lcom/android/server/am/AppCompactor$MemCompactionHandler; -Lcom/android/server/am/AppCompactor; +Lcom/android/server/am/CachedAppOptimizer$1; +Lcom/android/server/am/CachedAppOptimizer$MemCompactionHandler; +Lcom/android/server/am/CachedAppOptimizer; Lcom/android/server/am/AppErrorDialog$Data; Lcom/android/server/am/AppErrorResult; Lcom/android/server/am/AppErrors$BadProcessInfo; diff --git a/services/art-profile-boot b/services/art-profile-boot index e09424bc261c..fe4178ac4a50 100644 --- a/services/art-profile-boot +++ b/services/art-profile-boot @@ -538,7 +538,7 @@ Lcom/android/server/wm/WindowProcessController;->setCurrentProcState(I)V Lcom/android/server/am/ActivityManagerService;->updateLowMemStateLocked(III)Z Lcom/android/server/wm/ConfigurationContainer;->getWindowConfiguration()Landroid/app/WindowConfiguration; Lcom/android/server/am/OomAdjuster;->applyOomAdjLocked(Lcom/android/server/am/ProcessRecord;ZJJ)Z -Lcom/android/server/am/AppCompactor;->useCompaction()Z +Lcom/android/server/am/CachedAppOptimizer;->useCompaction()Z Lcom/android/server/am/ProcessList;->procStatesDifferForMem(II)Z Lcom/android/server/am/ActivityManagerService;->dispatchUidsChanged()V Lcom/android/server/audio/AudioService$VolumeStreamState;->setIndex(IILjava/lang/String;)Z diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index f34b5e71ad7b..cdd75107db24 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -19,6 +19,7 @@ package com.android.server.autofill; import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES; import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST; import static android.service.autofill.FillRequest.INVALID_REQUEST_ID; +import static android.view.autofill.AutofillManager.ACTION_RESPONSE_EXPIRED; import static android.view.autofill.AutofillManager.ACTION_START_SESSION; import static android.view.autofill.AutofillManager.ACTION_VALUE_CHANGED; import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED; @@ -162,6 +163,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** uid the session is for */ public final int uid; + /** user id the session is for */ + public final int userId; + /** ID of the task associated with this session's activity */ public final int taskId; @@ -266,6 +270,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private final LocalLog mWtfHistory; + @GuardedBy("mLock") + private boolean mExpiredResponse; + /** * Map of {@link MetricsEvent#AUTOFILL_REQUEST} metrics, keyed by fill request id. */ @@ -613,7 +620,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState int newState, int flags) { if (isInlineSuggestionsEnabled()) { mInlineSuggestionsRequestCallback = new InlineSuggestionsRequestCallbackImpl(); - mInputMethodManagerInternal.onCreateInlineSuggestionsRequest( + mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(userId, mComponentName, mCurrentViewId, mInlineSuggestionsRequestCallback); } @@ -687,6 +694,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private void requestNewFillResponseLocked(@NonNull ViewState viewState, int newState, int flags) { + mExpiredResponse = false; if (mForAugmentedAutofillOnly || mRemoteFillService == null) { if (sVerbose) { Slog.v(TAG, "requestNewFillResponse(): triggering augmented autofill instead " @@ -759,6 +767,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mFlags = flags; this.taskId = taskId; this.uid = uid; + this.userId = userId; mStartTime = SystemClock.elapsedRealtime(); mService = service; mLock = lock; @@ -1303,6 +1312,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } + // The client becomes invisible for the authentication, the response is effective. + mExpiredResponse = false; + final Parcelable result = data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT); final Bundle newClientState = data.getBundle(AutofillManager.EXTRA_CLIENT_STATE); if (sDebug) { @@ -2318,16 +2330,18 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * @param id The id of the view that is entered. * @param viewState The view that is entered. * @param flags The flag that was passed by the AutofillManager. + * + * @return {@code true} if a new fill response is requested. */ @GuardedBy("mLock") - private void requestNewFillResponseOnViewEnteredIfNecessaryLocked(@NonNull AutofillId id, + private boolean requestNewFillResponseOnViewEnteredIfNecessaryLocked(@NonNull AutofillId id, @NonNull ViewState viewState, int flags) { if ((flags & FLAG_MANUAL_REQUEST) != 0) { mForAugmentedAutofillOnly = false; if (sDebug) Slog.d(TAG, "Re-starting session on view " + id + " and flags " + flags); maybeRequestInlineSuggestionsRequestThenFillLocked(viewState, ViewState.STATE_RESTARTED_SESSION, flags); - return; + return true; } // If it's not, then check if it it should start a partition. @@ -2338,12 +2352,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } maybeRequestInlineSuggestionsRequestThenFillLocked(viewState, ViewState.STATE_STARTED_PARTITION, flags); + return true; } else { if (sVerbose) { Slog.v(TAG, "Not starting new partition for view " + id + ": " + viewState.getStateAsString()); } } + return false; } /** @@ -2351,7 +2367,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * * @param id The id of the view that is entered * - * @return {@code true} iff a new partition should be started + * @return {@code true} if a new partition should be started */ @GuardedBy("mLock") private boolean shouldStartNewPartitionLocked(@NonNull AutofillId id) { @@ -2359,6 +2375,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return true; } + if (mExpiredResponse) { + if (sDebug) { + Slog.d(TAG, "Starting a new partition because the response has expired."); + } + return true; + } + final int numResponses = mResponses.size(); if (numResponses >= AutofillManagerService.getPartitionMaxCount()) { Slog.e(TAG, "Not starting a new partition on " + id + " because session " + this.id @@ -2410,6 +2433,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState + id + " destroyed"); return; } + if (action == ACTION_RESPONSE_EXPIRED) { + mExpiredResponse = true; + if (sDebug) { + Slog.d(TAG, "Set the response has expired."); + } + return; + } + id.setSessionId(this.id); if (sVerbose) { Slog.v(TAG, "updateLocked(" + this.id + "): id=" + id + ", action=" @@ -2573,7 +2604,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return; } - requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags); + if (requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags)) { + return; + } if (isSameViewEntered) { return; @@ -3674,6 +3707,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return "VIEW_EXITED"; case ACTION_VALUE_CHANGED: return "VALUE_CHANGED"; + case ACTION_RESPONSE_EXPIRED: + return "RESPONSE_EXPIRED"; default: return "UNKNOWN_" + action; } diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java index 17cb7391b25a..1e3ee888a272 100644 --- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java @@ -16,24 +16,36 @@ package com.android.server.autofill.ui; +import static android.app.slice.SliceItem.FORMAT_IMAGE; +import static android.app.slice.SliceItem.FORMAT_TEXT; + import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.slice.Slice; +import android.app.slice.SliceItem; import android.content.Context; -import android.graphics.Color; import android.graphics.PixelFormat; +import android.graphics.drawable.Icon; import android.os.IBinder; import android.service.autofill.Dataset; import android.util.Log; import android.util.Slog; +import android.view.LayoutInflater; import android.view.SurfaceControl; import android.view.SurfaceControlViewHost; import android.view.View; +import android.view.ViewGroup; import android.view.WindowManager; import android.view.autofill.AutofillId; import android.view.autofill.AutofillValue; +import android.widget.ImageView; import android.widget.TextView; +import com.android.internal.R; + +import java.util.List; + /** * This is a temporary inline suggestion UI inflater which will be replaced by the ExtServices * implementation. @@ -72,18 +84,66 @@ public class InlineSuggestionUi { mContext.getDisplay(), (IBinder) null); final SurfaceControl sc = wvr.getSurfacePackage().getSurfaceControl(); - TextView textView = new TextView(mContext); - textView.setText(datasetValue.getTextValue()); - textView.setBackgroundColor(Color.WHITE); - textView.setTextColor(Color.BLACK); - if (onClickListener != null) { - textView.setOnClickListener(onClickListener); - } + final ViewGroup suggestionView = + (ViewGroup) renderSlice(dataset.getFieldInlinePresentation(index).getSlice()); WindowManager.LayoutParams lp = new WindowManager.LayoutParams(width, height, WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.TRANSPARENT); - wvr.addView(textView, lp); + wvr.addView(suggestionView, lp); return sc; } + + private View renderSlice(Slice slice) { + final LayoutInflater inflater = LayoutInflater.from(mContext); + final ViewGroup suggestionView = + (ViewGroup) inflater.inflate(R.layout.autofill_inline_suggestion, null); + + final ImageView startIconView = + suggestionView.findViewById(R.id.autofill_inline_suggestion_start_icon); + final TextView titleView = + suggestionView.findViewById(R.id.autofill_inline_suggestion_title); + final TextView subtitleView = + suggestionView.findViewById(R.id.autofill_inline_suggestion_subtitle); + final ImageView endIconView = + suggestionView.findViewById(R.id.autofill_inline_suggestion_end_icon); + + boolean hasStartIcon = false; + boolean hasEndIcon = false; + boolean hasSubtitle = false; + final List<SliceItem> sliceItems = slice.getItems(); + for (int i = 0; i < sliceItems.size(); i++) { + final SliceItem sliceItem = sliceItems.get(i); + if (sliceItem.getFormat().equals(FORMAT_IMAGE)) { + final Icon sliceIcon = sliceItem.getIcon(); + if (i == 0) { // start icon + startIconView.setImageIcon(sliceIcon); + hasStartIcon = true; + } else { // end icon + endIconView.setImageIcon(sliceIcon); + hasEndIcon = true; + } + } else if (sliceItem.getFormat().equals(FORMAT_TEXT)) { + final List<String> sliceHints = sliceItem.getHints(); + final String sliceText = sliceItem.getText().toString(); + if (sliceHints.contains("inline_title")) { // title + titleView.setText(sliceText); + } else { // subtitle + subtitleView.setText(sliceText); + hasSubtitle = true; + } + } + } + if (!hasStartIcon) { + startIconView.setVisibility(View.GONE); + } + if (!hasEndIcon) { + endIconView.setVisibility(View.GONE); + } + if (!hasSubtitle) { + subtitleView.setVisibility(View.GONE); + } + + return suggestionView; + } } diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index 7b95ab526b41..2c229b443f91 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -2538,7 +2538,6 @@ public class UserBackupManagerService { KeyValueBackupJob.schedule(mUserId, mContext, mConstants); } else { if (DEBUG) Slog.v(TAG, "Scheduling immediate backup pass"); - synchronized (mQueueLock) { // Fire the intent that kicks off the whole shebang... try { mRunBackupIntent.send(); @@ -2546,10 +2545,8 @@ public class UserBackupManagerService { // should never happen Slog.e(TAG, "run-backup intent cancelled!"); } - // ...and cancel any pending scheduled job, because we've just superseded it KeyValueBackupJob.cancel(mUserId, mContext); - } } } finally { Binder.restoreCallingIdentity(oldId); diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java index eb6262094849..b06fc52a24c2 100644 --- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java +++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java @@ -21,12 +21,10 @@ import static com.android.server.backup.BackupManagerService.MORE_DEBUG; import static com.android.server.backup.BackupManagerService.TAG; import android.app.backup.RestoreSet; -import android.content.Intent; import android.os.Handler; import android.os.HandlerThread; import android.os.Message; import android.os.RemoteException; -import android.os.UserHandle; import android.util.EventLog; import android.util.Pair; import android.util.Slog; @@ -40,7 +38,6 @@ import com.android.server.backup.DataChangedJournal; import com.android.server.backup.TransportManager; import com.android.server.backup.UserBackupManagerService; import com.android.server.backup.fullbackup.PerformAdbBackupTask; -import com.android.server.backup.fullbackup.PerformFullTransportBackupTask; import com.android.server.backup.keyvalue.BackupRequest; import com.android.server.backup.keyvalue.KeyValueBackupTask; import com.android.server.backup.params.AdbBackupParams; @@ -73,10 +70,7 @@ public class BackupHandler extends Handler { public static final int MSG_RESTORE_SESSION_TIMEOUT = 8; public static final int MSG_FULL_CONFIRMATION_TIMEOUT = 9; public static final int MSG_RUN_ADB_RESTORE = 10; - public static final int MSG_RETRY_INIT = 11; public static final int MSG_RETRY_CLEAR = 12; - public static final int MSG_WIDGET_BROADCAST = 13; - public static final int MSG_RUN_FULL_TRANSPORT_BACKUP = 14; public static final int MSG_REQUEST_BACKUP = 15; public static final int MSG_SCHEDULE_BACKUP_PACKAGE = 16; public static final int MSG_BACKUP_OPERATION_TIMEOUT = 17; @@ -279,12 +273,6 @@ public class BackupHandler extends Handler { break; } - case MSG_RUN_FULL_TRANSPORT_BACKUP: { - PerformFullTransportBackupTask task = (PerformFullTransportBackupTask) msg.obj; - (new Thread(task, "transport-backup")).start(); - break; - } - case MSG_RUN_RESTORE: { RestoreParams params = (RestoreParams) msg.obj; Slog.d(TAG, "MSG_RUN_RESTORE observer=" + params.observer); @@ -445,12 +433,6 @@ public class BackupHandler extends Handler { break; } - case MSG_WIDGET_BROADCAST: { - final Intent intent = (Intent) msg.obj; - backupManagerService.getContext().sendBroadcastAsUser(intent, UserHandle.SYSTEM); - break; - } - case MSG_REQUEST_BACKUP: { BackupParams params = (BackupParams) msg.obj; if (MORE_DEBUG) { diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java index 2f8c506d5ea7..f3647602e69b 100644 --- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java +++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java @@ -23,8 +23,6 @@ import android.content.res.Configuration; import android.os.UserHandle; import android.os.UserManager; -import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener; - import java.util.List; import java.util.Set; @@ -198,6 +196,12 @@ public abstract class UsageStatsManagerInternal { long beginTime, long endTime, boolean obfuscateInstantApps); /** + * Returns the events for the user in the given time period. + */ + public abstract UsageEvents queryEventsForUser(@UserIdInt int userId, long beginTime, + long endTime, boolean shouldObfuscateInstantApps); + + /** * Used to persist the last time a job was run for this app, in order to make decisions later * whether a job should be deferred until later. The time passed in should be in elapsed * realtime since boot. diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index 7be3d11f4c68..0f2fb9252c29 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -208,6 +208,7 @@ class AlarmManagerService extends SystemService { AppWakeupHistory mAppWakeupHistory; ClockReceiver mClockReceiver; final DeliveryTracker mDeliveryTracker = new DeliveryTracker(); + IBinder.DeathRecipient mListenerDeathRecipient; Intent mTimeTickIntent; IAlarmListener mTimeTickTrigger; PendingIntent mDateChangeSender; @@ -1447,6 +1448,18 @@ class AlarmManagerService extends SystemService { public void onStart() { mInjector.init(); + mListenerDeathRecipient = new IBinder.DeathRecipient() { + @Override + public void binderDied() { + } + + @Override + public void binderDied(IBinder who) { + final IAlarmListener listener = IAlarmListener.Stub.asInterface(who); + removeImpl(null, listener); + } + }; + synchronized (mLock) { mHandler = new AlarmHandler(); mConstants = new Constants(mHandler); @@ -1653,6 +1666,15 @@ class AlarmManagerService extends SystemService { return; } + if (directReceiver != null) { + try { + directReceiver.asBinder().linkToDeath(mListenerDeathRecipient, 0); + } catch (RemoteException e) { + Slog.w(TAG, "Dropping unreachable alarm listener " + listenerTag); + return; + } + } + // 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) { diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 26c12c141e19..c1e23e42c14f 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -79,7 +79,6 @@ import android.net.InetAddresses; import android.net.IpMemoryStore; import android.net.IpPrefix; import android.net.LinkProperties; -import android.net.LinkProperties.CompareResult; import android.net.MatchAllNetworkSpecifier; import android.net.NattSocketKeepalive; import android.net.Network; @@ -87,7 +86,6 @@ import android.net.NetworkAgent; import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.NetworkConfig; -import android.net.NetworkFactory; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; import android.net.NetworkMonitorManager; @@ -114,6 +112,7 @@ import android.net.metrics.IpConnectivityLog; import android.net.metrics.NetworkEvent; import android.net.netlink.InetDiagMessage; import android.net.shared.PrivateDnsConfig; +import android.net.util.LinkPropertiesUtils.CompareResult; import android.net.util.MultinetworkPolicyTracker; import android.net.util.NetdService; import android.os.Binder; @@ -2843,26 +2842,11 @@ public class ConnectivityService extends IConnectivityManager.Stub return true; } - // TODO: delete when direct use of registerNetworkFactory is no longer supported. - private boolean maybeHandleNetworkFactoryMessage(Message msg) { - switch (msg.what) { - default: - return false; - case NetworkFactory.EVENT_UNFULFILLABLE_REQUEST: { - handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.sendingUid, - /* callOnUnavailable */ true); - break; - } - } - return true; - } - @Override public void handleMessage(Message msg) { if (!maybeHandleAsyncChannelMessage(msg) && !maybeHandleNetworkMonitorMessage(msg) - && !maybeHandleNetworkAgentInfoMessage(msg) - && !maybeHandleNetworkFactoryMessage(msg)) { + && !maybeHandleNetworkAgentInfoMessage(msg)) { maybeHandleNetworkAgentMessage(msg); } } @@ -3617,14 +3601,29 @@ public class ConnectivityService extends IConnectivityManager.Stub enforceSettingsPermission(); } + final NetworkMonitorManager nm = getNetworkMonitorManager(mNetwork); + if (nm == null) return; + nm.notifyCaptivePortalAppFinished(response); + } + + @Override + public void appRequest(final int request) { + final NetworkMonitorManager nm = getNetworkMonitorManager(mNetwork); + if (nm == null) return; + + if (request == CaptivePortal.APP_REQUEST_REEVALUATION_REQUIRED) { + nm.forceReevaluation(Binder.getCallingUid()); + } + } + + @Nullable + private NetworkMonitorManager getNetworkMonitorManager(final Network network) { // getNetworkAgentInfoForNetwork is thread-safe - final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(mNetwork); - if (nai == null) return; + final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); + if (nai == null) return null; // nai.networkMonitor() is thread-safe - final NetworkMonitorManager nm = nai.networkMonitor(); - if (nm == null) return; - nm.notifyCaptivePortalAppFinished(response); + return nai.networkMonitor(); } @Override @@ -5490,7 +5489,10 @@ public class ConnectivityService extends IConnectivityManager.Stub // changes that would conflict throughout the automerger graph. Having this method temporarily // helps with the process of going through with all these dependent changes across the entire // tree. - public int registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo, + /** + * Register a new agent. {@see #registerNetworkAgent} below. + */ + public Network registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo, LinkProperties linkProperties, NetworkCapabilities networkCapabilities, int currentScore, NetworkAgentConfig networkAgentConfig) { return registerNetworkAgent(messenger, networkInfo, linkProperties, networkCapabilities, @@ -5511,8 +5513,9 @@ public class ConnectivityService extends IConnectivityManager.Stub * {@link NetworkAgentInfo#getCurrentScore}. * @param networkAgentConfig metadata about the network. This is never updated. * @param providerId the ID of the provider owning this NetworkAgent. + * @return the network created for this agent. */ - public int registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo, + public Network registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo, LinkProperties linkProperties, NetworkCapabilities networkCapabilities, int currentScore, NetworkAgentConfig networkAgentConfig, int providerId) { enforceNetworkFactoryPermission(); @@ -5545,7 +5548,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // If the network disconnects or sends any other event before that, messages are deferred by // NetworkAgent until nai.asyncChannel.connect(), which will be called when finalizing the // registration. - return nai.network.netId; + return nai.network; } private void handleRegisterNetworkAgent(NetworkAgentInfo nai, INetworkMonitor networkMonitor) { diff --git a/services/core/java/com/android/server/DynamicSystemService.java b/services/core/java/com/android/server/DynamicSystemService.java index 7909e3096cbe..c60460fccb76 100644 --- a/services/core/java/com/android/server/DynamicSystemService.java +++ b/services/core/java/com/android/server/DynamicSystemService.java @@ -44,9 +44,9 @@ public class DynamicSystemService extends IDynamicSystemService.Stub implements private static final String TAG = "DynamicSystemService"; private static final String NO_SERVICE_ERROR = "no gsiservice"; private static final int GSID_ROUGH_TIMEOUT_MS = 8192; - private static final String PATH_DEFAULT = "/data/gsi"; + private static final String PATH_DEFAULT = "/data/gsi/"; private Context mContext; - private String mInstallPath; + private String mInstallPath, mDsuSlot; private volatile IGsiService mGsiService; DynamicSystemService(Context context) { @@ -115,7 +115,7 @@ public class DynamicSystemService extends IDynamicSystemService.Stub implements } @Override - public boolean startInstallation() throws RemoteException { + public boolean startInstallation(String dsuSlot) throws RemoteException { IGsiService service = getGsiService(); // priority from high to low: sysprop -> sdcard -> /data String path = SystemProperties.get("os.aot.path"); @@ -129,16 +129,17 @@ public class DynamicSystemService extends IDynamicSystemService.Stub implements if (!Environment.MEDIA_MOUNTED.equals(volume.getState())) continue; File sdCard = volume.getPathFile(); if (sdCard.isDirectory()) { - path = sdCard.getPath(); + path = new File(sdCard, dsuSlot).getPath(); break; } } if (path.isEmpty()) { - path = PATH_DEFAULT; + path = PATH_DEFAULT + dsuSlot; } Slog.i(TAG, "startInstallation -> " + path); } mInstallPath = path; + mDsuSlot = dsuSlot; if (service.openInstall(path) != 0) { Slog.i(TAG, "Failed to open " + path); return false; @@ -203,7 +204,7 @@ public class DynamicSystemService extends IDynamicSystemService.Stub implements public boolean setEnable(boolean enable, boolean oneShot) throws RemoteException { IGsiService gsiService = getGsiService(); if (enable) { - return gsiService.enableGsi(oneShot) == 0; + return gsiService.enableGsi(oneShot, mDsuSlot) == 0; } else { return gsiService.disableGsi(); } diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index dc393d1609de..e9db9c819ab7 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -16,6 +16,8 @@ package com.android.server; +import static android.Manifest.permission.ACCESS_COARSE_LOCATION; +import static android.Manifest.permission.ACCESS_FINE_LOCATION; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.location.LocationManager.FUSED_PROVIDER; import static android.location.LocationManager.GPS_PROVIDER; @@ -83,6 +85,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageMonitor; import com.android.internal.location.ProviderProperties; import com.android.internal.location.ProviderRequest; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; @@ -459,8 +462,10 @@ public class LocationManagerService extends ILocationManager.Stub { Log.d(TAG, "[u" + userId + "] location enabled = " + isLocationEnabledForUser(userId)); } - Intent intent = new Intent(LocationManager.MODE_CHANGED_ACTION); - intent.putExtra(LocationManager.EXTRA_LOCATION_ENABLED, isLocationEnabledForUser(userId)); + Intent intent = new Intent(LocationManager.MODE_CHANGED_ACTION) + .putExtra(LocationManager.EXTRA_LOCATION_ENABLED, isLocationEnabledForUser(userId)) + .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); mContext.sendBroadcastAsUser(intent, UserHandle.of(userId)); for (LocationProviderManager manager : mProviderManagers) { @@ -929,9 +934,11 @@ public class LocationManagerService extends ILocationManager.Stub { // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility mSettingsStore.setLocationProviderAllowed(mName, useable, userId); - Intent intent = new Intent(LocationManager.PROVIDERS_CHANGED_ACTION); - intent.putExtra(LocationManager.EXTRA_PROVIDER_NAME, mName); - intent.putExtra(LocationManager.EXTRA_PROVIDER_ENABLED, useable); + Intent intent = new Intent(LocationManager.PROVIDERS_CHANGED_ACTION) + .putExtra(LocationManager.EXTRA_PROVIDER_NAME, mName) + .putExtra(LocationManager.EXTRA_PROVIDER_ENABLED, useable) + .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); mContext.sendBroadcastAsUser(intent, UserHandle.of(userId)); } @@ -1396,20 +1403,19 @@ public class LocationManagerService extends ILocationManager.Stub { private String getResolutionPermission(int resolutionLevel) { switch (resolutionLevel) { case RESOLUTION_LEVEL_FINE: - return android.Manifest.permission.ACCESS_FINE_LOCATION; + return ACCESS_FINE_LOCATION; case RESOLUTION_LEVEL_COARSE: - return android.Manifest.permission.ACCESS_COARSE_LOCATION; + return ACCESS_COARSE_LOCATION; default: return null; } } private int getAllowedResolutionLevel(int pid, int uid) { - if (mContext.checkPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, - pid, uid) == PERMISSION_GRANTED) { + if (mContext.checkPermission(ACCESS_FINE_LOCATION, pid, uid) == PERMISSION_GRANTED) { return RESOLUTION_LEVEL_FINE; - } else if (mContext.checkPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION, - pid, uid) == PERMISSION_GRANTED) { + } else if (mContext.checkPermission(ACCESS_COARSE_LOCATION, pid, uid) + == PERMISSION_GRANTED) { return RESOLUTION_LEVEL_COARSE; } else { return RESOLUTION_LEVEL_NONE; @@ -1420,59 +1426,28 @@ public class LocationManagerService extends ILocationManager.Stub { return getAllowedResolutionLevel(Binder.getCallingPid(), Binder.getCallingUid()); } - private void checkResolutionLevelIsSufficientForGeofenceUse(int allowedResolutionLevel) { - if (allowedResolutionLevel < RESOLUTION_LEVEL_FINE) { - throw new SecurityException("Geofence usage requires ACCESS_FINE_LOCATION permission"); - } + private boolean checkCallingOrSelfLocationPermission() { + return mContext.checkCallingOrSelfPermission(ACCESS_COARSE_LOCATION) == PERMISSION_GRANTED + || mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION) + == PERMISSION_GRANTED; } - @GuardedBy("mLock") - private int getMinimumResolutionLevelForProviderUseLocked(String provider) { - if (GPS_PROVIDER.equals(provider) || PASSIVE_PROVIDER.equals(provider)) { - // gps and passive providers require FINE permission - return RESOLUTION_LEVEL_FINE; - } else if (NETWORK_PROVIDER.equals(provider) || FUSED_PROVIDER.equals(provider)) { - // network and fused providers are ok with COARSE or FINE - return RESOLUTION_LEVEL_COARSE; - } else { - for (LocationProviderManager lp : mProviderManagers) { - if (!lp.getName().equals(provider)) { - continue; - } - - ProviderProperties properties = lp.getProperties(); - if (properties != null) { - if (properties.mRequiresSatellite) { - // provider requiring satellites require FINE permission - return RESOLUTION_LEVEL_FINE; - } else if (properties.mRequiresNetwork || properties.mRequiresCell) { - // provider requiring network and or cell require COARSE or FINE - return RESOLUTION_LEVEL_COARSE; - } - } - } + private void enforceCallingOrSelfLocationPermission() { + if (checkCallingOrSelfLocationPermission()) { + return; } - return RESOLUTION_LEVEL_FINE; // if in doubt, require FINE + throw new SecurityException("uid " + Binder.getCallingUid() + " does not have " + + ACCESS_COARSE_LOCATION + " or " + ACCESS_FINE_LOCATION + "."); } - @GuardedBy("mLock") - private void checkResolutionLevelIsSufficientForProviderUseLocked(int allowedResolutionLevel, - String providerName) { - int requiredResolutionLevel = getMinimumResolutionLevelForProviderUseLocked(providerName); - if (allowedResolutionLevel < requiredResolutionLevel) { - switch (requiredResolutionLevel) { - case RESOLUTION_LEVEL_FINE: - throw new SecurityException("\"" + providerName + "\" location provider " + - "requires ACCESS_FINE_LOCATION permission."); - case RESOLUTION_LEVEL_COARSE: - throw new SecurityException("\"" + providerName + "\" location provider " + - "requires ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission."); - default: - throw new SecurityException("Insufficient permission for \"" + providerName + - "\" location provider."); - } + private void enforceCallingOrSelfPackageName(String packageName) { + int uid = Binder.getCallingUid(); + if (ArrayUtils.contains(mPackageManager.getPackagesForUid(uid), packageName)) { + return; } + + throw new SecurityException("invalid package \"" + packageName + "\" for uid " + uid); } public static int resolutionLevelToOp(int allowedResolutionLevel) { @@ -1548,7 +1523,10 @@ public class LocationManagerService extends ILocationManager.Stub { */ @Override public List<String> getProviders(Criteria criteria, boolean enabledOnly) { - int allowedResolutionLevel = getCallerAllowedResolutionLevel(); + if (!checkCallingOrSelfLocationPermission()) { + return Collections.emptyList(); + } + synchronized (mLock) { ArrayList<String> providers = new ArrayList<>(mProviderManagers.size()); for (LocationProviderManager manager : mProviderManagers) { @@ -1556,9 +1534,6 @@ public class LocationManagerService extends ILocationManager.Stub { if (FUSED_PROVIDER.equals(name)) { continue; } - if (allowedResolutionLevel < getMinimumResolutionLevelForProviderUseLocked(name)) { - continue; - } if (enabledOnly && !manager.isUseable()) { continue; } @@ -2002,33 +1977,18 @@ public class LocationManagerService extends ILocationManager.Stub { return sanitizedRequest; } - private void checkPackageName(String packageName) { - if (packageName == null) { - throw new SecurityException("invalid package name: " + null); - } - int uid = Binder.getCallingUid(); - String[] packages = mPackageManager.getPackagesForUid(uid); - if (packages == null) { - throw new SecurityException("invalid UID " + uid); - } - for (String pkg : packages) { - if (packageName.equals(pkg)) return; - } - throw new SecurityException("invalid package name: " + packageName); - } - @Override public void requestLocationUpdates(LocationRequest request, ILocationListener listener, PendingIntent intent, String packageName, String featureId, String listenerIdentifier) { Objects.requireNonNull(listenerIdentifier); + enforceCallingOrSelfLocationPermission(); + enforceCallingOrSelfPackageName(packageName); + synchronized (mLock) { if (request == null) request = DEFAULT_LOCATION_REQUEST; - checkPackageName(packageName); int allowedResolutionLevel = getCallerAllowedResolutionLevel(); - checkResolutionLevelIsSufficientForProviderUseLocked(allowedResolutionLevel, - request.getProvider()); WorkSource workSource = request.getWorkSource(); if (workSource != null && !workSource.isEmpty()) { mContext.enforceCallingOrSelfPermission( @@ -2135,7 +2095,7 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public void removeUpdates(ILocationListener listener, PendingIntent intent, String packageName) { - checkPackageName(packageName); + enforceCallingOrSelfPackageName(packageName); int pid = Binder.getCallingPid(); int uid = Binder.getCallingUid(); @@ -2197,12 +2157,12 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public Location getLastLocation(LocationRequest r, String packageName, String featureId) { + enforceCallingOrSelfLocationPermission(); + enforceCallingOrSelfPackageName(packageName); + synchronized (mLock) { LocationRequest request = r != null ? r : DEFAULT_LOCATION_REQUEST; int allowedResolutionLevel = getCallerAllowedResolutionLevel(); - checkPackageName(packageName); - checkResolutionLevelIsSufficientForProviderUseLocked(allowedResolutionLevel, - request.getProvider()); // no need to sanitize this request, as only the provider name is used final int pid = Binder.getCallingPid(); @@ -2348,7 +2308,7 @@ public class LocationManagerService extends ILocationManager.Stub { public boolean injectLocation(Location location) { mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE, "Location Hardware permission not granted to inject location"); - mContext.enforceCallingPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, + mContext.enforceCallingPermission(ACCESS_FINE_LOCATION, "Access Fine Location permission not granted to inject Location"); synchronized (mLock) { @@ -2374,17 +2334,14 @@ public class LocationManagerService extends ILocationManager.Stub { String packageName, String featureId, String listenerIdentifier) { Objects.requireNonNull(listenerIdentifier); + mContext.enforceCallingOrSelfPermission(ACCESS_FINE_LOCATION, null); + enforceCallingOrSelfPackageName(packageName); + if (request == null) request = DEFAULT_LOCATION_REQUEST; int allowedResolutionLevel = getCallerAllowedResolutionLevel(); - checkResolutionLevelIsSufficientForGeofenceUse(allowedResolutionLevel); if (intent == null) { throw new IllegalArgumentException("invalid pending intent: " + null); } - checkPackageName(packageName); - synchronized (mLock) { - checkResolutionLevelIsSufficientForProviderUseLocked(allowedResolutionLevel, - request.getProvider()); - } // Require that caller can manage given document boolean callerHasLocationHardwarePermission = mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE) @@ -2430,7 +2387,7 @@ public class LocationManagerService extends ILocationManager.Stub { if (intent == null) { throw new IllegalArgumentException("invalid pending intent: " + null); } - checkPackageName(packageName); + enforceCallingOrSelfPackageName(packageName); if (D) Log.d(TAG, "removeGeofence: " + geofence + " " + intent); @@ -2517,36 +2474,30 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public boolean sendExtraCommand(String providerName, String command, Bundle extras) { - if (providerName == null) { - // throw NullPointerException to remain compatible with previous implementation - throw new NullPointerException(); - } + Objects.requireNonNull(providerName); + Objects.requireNonNull(command); mContext.enforceCallingOrSelfPermission( Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, null); + enforceCallingOrSelfLocationPermission(); - synchronized (mLock) { - checkResolutionLevelIsSufficientForProviderUseLocked(getCallerAllowedResolutionLevel(), - providerName); - - mLocationUsageLogger.logLocationApiUsage( - LocationStatsEnums.USAGE_STARTED, - LocationStatsEnums.API_SEND_EXTRA_COMMAND, - providerName); + mLocationUsageLogger.logLocationApiUsage( + LocationStatsEnums.USAGE_STARTED, + LocationStatsEnums.API_SEND_EXTRA_COMMAND, + providerName); - LocationProviderManager manager = getLocationProviderManager(providerName); - if (manager != null) { - manager.sendExtraCommand(Binder.getCallingUid(), Binder.getCallingPid(), command, - extras); - } + LocationProviderManager manager = getLocationProviderManager(providerName); + if (manager != null) { + manager.sendExtraCommand(Binder.getCallingUid(), Binder.getCallingPid(), command, + extras); + } - mLocationUsageLogger.logLocationApiUsage( - LocationStatsEnums.USAGE_ENDED, - LocationStatsEnums.API_SEND_EXTRA_COMMAND, - providerName); + mLocationUsageLogger.logLocationApiUsage( + LocationStatsEnums.USAGE_ENDED, + LocationStatsEnums.API_SEND_EXTRA_COMMAND, + providerName); - return true; - } + return true; } @Override diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java index b26ef92557e1..d1d1cb3566d2 100644 --- a/services/core/java/com/android/server/NetworkScoreService.java +++ b/services/core/java/com/android/server/NetworkScoreService.java @@ -523,7 +523,7 @@ public class NetworkScoreService extends INetworkScoreService.Stub { @Override public void accept(INetworkScoreCache networkScoreCache, Object cookie) { - int filterType = NetworkScoreManager.CACHE_FILTER_NONE; + int filterType = NetworkScoreManager.SCORE_FILTER_NONE; if (cookie instanceof Integer) { filterType = (Integer) cookie; } @@ -547,17 +547,17 @@ public class NetworkScoreService extends INetworkScoreService.Stub { private List<ScoredNetwork> filterScores(List<ScoredNetwork> scoredNetworkList, int filterType) { switch (filterType) { - case NetworkScoreManager.CACHE_FILTER_NONE: + case NetworkScoreManager.SCORE_FILTER_NONE: return scoredNetworkList; - case NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK: + case NetworkScoreManager.SCORE_FILTER_CURRENT_NETWORK: if (mCurrentNetworkFilter == null) { mCurrentNetworkFilter = new CurrentNetworkScoreCacheFilter(new WifiInfoSupplier(mContext)); } return mCurrentNetworkFilter.apply(scoredNetworkList); - case NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS: + case NetworkScoreManager.SCORE_FILTER_SCAN_RESULTS: if (mScanResultsFilter == null) { mScanResultsFilter = new ScanResultsScoreCacheFilter( new ScanResultsSupplier(mContext)); diff --git a/services/core/java/com/android/server/NsdService.java b/services/core/java/com/android/server/NsdService.java index b9b7bf73c1e6..4a1820a8e538 100644 --- a/services/core/java/com/android/server/NsdService.java +++ b/services/core/java/com/android/server/NsdService.java @@ -22,10 +22,10 @@ import android.content.Intent; import android.database.ContentObserver; import android.net.NetworkStack; import android.net.Uri; -import android.net.nsd.DnsSdTxtRecord; import android.net.nsd.INsdManager; import android.net.nsd.NsdManager; import android.net.nsd.NsdServiceInfo; +import android.net.util.nsd.DnsSdTxtRecord; import android.os.Handler; import android.os.HandlerThread; import android.os.Message; diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java index 7b4fd37f01c9..b464422e9e3d 100644 --- a/services/core/java/com/android/server/PackageWatchdog.java +++ b/services/core/java/com/android/server/PackageWatchdog.java @@ -29,6 +29,7 @@ import android.net.ConnectivityModuleConnector; import android.os.Environment; import android.os.Handler; import android.os.Looper; +import android.os.Process; import android.os.SystemProperties; import android.provider.DeviceConfig; import android.text.TextUtils; @@ -36,6 +37,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; import android.util.LongArrayQueue; +import android.util.MathUtils; import android.util.Slog; import android.util.Xml; @@ -117,6 +119,12 @@ public class PackageWatchdog { // Whether explicit health checks are enabled or not private static final boolean DEFAULT_EXPLICIT_HEALTH_CHECK_ENABLED = true; + @VisibleForTesting + static final int DEFAULT_BOOT_LOOP_TRIGGER_COUNT = 5; + static final long DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS = TimeUnit.MINUTES.toMillis(10); + private static final String PROP_RESCUE_BOOT_COUNT = "sys.rescue_boot_count"; + private static final String PROP_RESCUE_BOOT_START = "sys.rescue_boot_start"; + private long mNumberOfNativeCrashPollsRemaining; private static final int DB_VERSION = 1; @@ -152,6 +160,7 @@ public class PackageWatchdog { private final Runnable mSyncStateWithScheduledReason = this::syncStateWithScheduledReason; private final Runnable mSaveToFile = this::saveToFile; private final SystemClock mSystemClock; + private final BootThreshold mBootThreshold; @GuardedBy("mLock") private boolean mIsPackagesReady; // Flag to control whether explicit health checks are supported or not @@ -169,6 +178,7 @@ public class PackageWatchdog { @FunctionalInterface @VisibleForTesting interface SystemClock { + // TODO: Add elapsedRealtime to this interface long uptimeMillis(); } @@ -198,6 +208,8 @@ public class PackageWatchdog { mConnectivityModuleConnector = connectivityModuleConnector; mSystemClock = clock; mNumberOfNativeCrashPollsRemaining = NUMBER_OF_NATIVE_CRASH_POLLS; + mBootThreshold = new BootThreshold(DEFAULT_BOOT_LOOP_TRIGGER_COUNT, + DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS); loadFromFile(); sPackageWatchdog = this; } @@ -411,6 +423,35 @@ public class PackageWatchdog { } } + /** + * Called when the system server boots. If the system server is detected to be in a boot loop, + * query each observer and perform the mitigation action with the lowest user impact. + */ + public void noteBoot() { + synchronized (mLock) { + if (mBootThreshold.incrementAndTest()) { + mBootThreshold.reset(); + PackageHealthObserver currentObserverToNotify = null; + int currentObserverImpact = Integer.MAX_VALUE; + for (int i = 0; i < mAllObservers.size(); i++) { + final ObserverInternal observer = mAllObservers.valueAt(i); + PackageHealthObserver registeredObserver = observer.registeredObserver; + if (registeredObserver != null) { + int impact = registeredObserver.onBootLoop(); + if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE + && impact < currentObserverImpact) { + currentObserverToNotify = registeredObserver; + currentObserverImpact = impact; + } + } + } + if (currentObserverToNotify != null) { + currentObserverToNotify.executeBootLoopMitigation(); + } + } + } + } + // TODO(b/120598832): Optimize write? Maybe only write a separate smaller file? Also // avoid holding lock? // This currently adds about 7ms extra to shutdown thread @@ -519,6 +560,22 @@ public class PackageWatchdog { boolean execute(@Nullable VersionedPackage versionedPackage, @FailureReasons int failureReason); + + /** + * Called when the system server has booted several times within a window of time, defined + * by {@link #mBootThreshold} + */ + default @PackageHealthObserverImpact int onBootLoop() { + return PackageHealthObserverImpact.USER_IMPACT_NONE; + } + + /** + * Executes mitigation for {@link #onBootLoop} + */ + default boolean executeBootLoopMitigation() { + return false; + } + // TODO(b/120598832): Ensure uniqueness? /** * Identifier for the observer, should not change across device updates otherwise the @@ -1367,4 +1424,62 @@ public class PackageWatchdog { return value > 0 ? value : Long.MAX_VALUE; } } + + /** + * Handles the thresholding logic for system server boots. + */ + static class BootThreshold { + + private final int mBootTriggerCount; + private final long mTriggerWindow; + + BootThreshold(int bootTriggerCount, long triggerWindow) { + this.mBootTriggerCount = bootTriggerCount; + this.mTriggerWindow = triggerWindow; + } + + public void reset() { + setStart(0); + setCount(0); + } + + private int getCount() { + return SystemProperties.getInt(PROP_RESCUE_BOOT_COUNT, 0); + } + + private void setCount(int count) { + SystemProperties.set(PROP_RESCUE_BOOT_COUNT, Integer.toString(count)); + } + + public long getStart() { + return SystemProperties.getLong(PROP_RESCUE_BOOT_START, 0); + } + + public void setStart(long start) { + final long now = android.os.SystemClock.elapsedRealtime(); + final long newStart = MathUtils.constrain(start, 0, now); + SystemProperties.set(PROP_RESCUE_BOOT_START, Long.toString(newStart)); + } + + /** Increments the boot counter, and returns whether the device is bootlooping. */ + public boolean incrementAndTest() { + final long now = android.os.SystemClock.elapsedRealtime(); + if (now - getStart() < 0) { + Slog.e(TAG, "Window was less than zero. Resetting start to current time."); + setStart(now); + } + final long window = now - getStart(); + if (window >= mTriggerWindow) { + setCount(1); + setStart(now); + return false; + } else { + int count = getCount() + 1; + setCount(count); + EventLogTags.writeRescueNote(Process.ROOT_UID, count, window); + return count >= mBootTriggerCount; + } + } + + } } diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java index 3dafc64391fd..e8e3b39d5112 100644 --- a/services/core/java/com/android/server/RescueParty.java +++ b/services/core/java/com/android/server/RescueParty.java @@ -27,17 +27,16 @@ import android.content.pm.VersionedPackage; import android.os.Build; import android.os.Environment; import android.os.FileUtils; +import android.os.Process; import android.os.RecoverySystem; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings; -import android.text.format.DateUtils; import android.util.ExceptionUtils; import android.util.Log; import android.util.MathUtils; import android.util.Slog; -import android.util.SparseArray; import android.util.StatsLog; import com.android.internal.annotations.GuardedBy; @@ -80,12 +79,6 @@ public class RescueParty { static final int LEVEL_FACTORY_RESET = 4; @VisibleForTesting static final String PROP_RESCUE_BOOT_COUNT = "sys.rescue_boot_count"; - /** - * The boot trigger window size must always be greater than Watchdog's deadlock timeout - * {@link Watchdog#DEFAULT_TIMEOUT}. - */ - @VisibleForTesting - static final long BOOT_TRIGGER_WINDOW_MILLIS = 600 * DateUtils.SECOND_IN_MILLIS; @VisibleForTesting static final String TAG = "RescueParty"; @@ -93,18 +86,11 @@ public class RescueParty { private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue"; - private static final String PROP_RESCUE_BOOT_START = "sys.rescue_boot_start"; private static final String PROP_VIRTUAL_DEVICE = "ro.hardware.virtual_device"; private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT | ApplicationInfo.FLAG_SYSTEM; - - /** Threshold for boot loops */ - private static final Threshold sBoot = new BootThreshold(); - /** Threshold for app crash loops */ - private static SparseArray<Threshold> sApps = new SparseArray<>(); - /** Register the Rescue Party observer as a Package Watchdog health observer */ public static void registerHealthObserver(Context context) { PackageWatchdog.getInstance(context).registerHealthObserver( @@ -141,19 +127,6 @@ public class RescueParty { } /** - * Take note of a boot event. If we notice too many of these events - * happening in rapid succession, we'll send out a rescue party. - */ - public static void noteBoot(Context context) { - if (isDisabled()) return; - if (sBoot.incrementAndTest()) { - sBoot.reset(); - incrementRescueLevel(sBoot.uid); - executeRescueLevel(context); - } - } - - /** * Check if we're currently attempting to reboot for a factory reset. */ public static boolean isAttemptingFactoryReset() { @@ -170,11 +143,6 @@ public class RescueParty { } @VisibleForTesting - static void resetAllThresholds() { - sBoot.reset(); - } - - @VisibleForTesting static long getElapsedRealtime() { return SystemClock.elapsedRealtime(); } @@ -187,6 +155,14 @@ public class RescueParty { } /** + * Get the current rescue level. + */ + private static int getRescueLevel() { + return MathUtils.constrain(SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE), + LEVEL_NONE, LEVEL_FACTORY_RESET); + } + + /** * Escalate to the next rescue level. After incrementing the level you'll * probably want to call {@link #executeRescueLevel(Context)}. */ @@ -366,87 +342,26 @@ public class RescueParty { } @Override - public String getName() { - return NAME; - } - } - - /** - * Threshold that can be triggered if a number of events occur within a - * window of time. - */ - private abstract static class Threshold { - public abstract int getCount(); - public abstract void setCount(int count); - public abstract long getStart(); - public abstract void setStart(long start); - - private final int uid; - private final int triggerCount; - private final long triggerWindow; - - public Threshold(int uid, int triggerCount, long triggerWindow) { - this.uid = uid; - this.triggerCount = triggerCount; - this.triggerWindow = triggerWindow; - } - - public void reset() { - setCount(0); - setStart(0); - } - - /** - * @return if this threshold has been triggered - */ - public boolean incrementAndTest() { - final long now = getElapsedRealtime(); - final long window = now - getStart(); - if (window > triggerWindow) { - setCount(1); - setStart(now); - return false; - } else { - int count = getCount() + 1; - setCount(count); - EventLogTags.writeRescueNote(uid, count, window); - Slog.w(TAG, "Noticed " + count + " events for UID " + uid + " in last " - + (window / 1000) + " sec"); - return (count >= triggerCount); + public int onBootLoop() { + if (isDisabled()) { + return PackageHealthObserverImpact.USER_IMPACT_NONE; } - } - } - - /** - * Specialization of {@link Threshold} for monitoring boot events. It stores - * counters in system properties for robustness. - */ - private static class BootThreshold extends Threshold { - public BootThreshold() { - // We're interested in TRIGGER_COUNT events in any - // BOOT_TRIGGER_WINDOW_MILLIS second period; this window is super relaxed because - // booting can take a long time if forced to dexopt things. - super(android.os.Process.ROOT_UID, TRIGGER_COUNT, BOOT_TRIGGER_WINDOW_MILLIS); + return mapRescueLevelToUserImpact(getRescueLevel()); } @Override - public int getCount() { - return SystemProperties.getInt(PROP_RESCUE_BOOT_COUNT, 0); - } - - @Override - public void setCount(int count) { - SystemProperties.set(PROP_RESCUE_BOOT_COUNT, Integer.toString(count)); - } - - @Override - public long getStart() { - return SystemProperties.getLong(PROP_RESCUE_BOOT_START, 0); + public boolean executeBootLoopMitigation() { + if (isDisabled()) { + return false; + } + incrementRescueLevel(Process.ROOT_UID); + executeRescueLevel(mContext); + return true; } @Override - public void setStart(long start) { - SystemProperties.set(PROP_RESCUE_BOOT_START, Long.toString(start)); + public String getName() { + return NAME; } } diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index db542145a750..bcc3bdb0a232 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -44,6 +44,7 @@ import static com.android.internal.util.XmlUtils.readStringAttribute; import static com.android.internal.util.XmlUtils.writeIntAttribute; import static com.android.internal.util.XmlUtils.writeLongAttribute; import static com.android.internal.util.XmlUtils.writeStringAttribute; +import static com.android.server.storage.StorageUserConnection.REMOTE_TIMEOUT_SECONDS; import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.START_TAG; @@ -1985,16 +1986,28 @@ class StorageManagerService extends IStorageManager.Stub Slog.i(TAG, "Mounting volume " + vol); mVold.mount(vol.id, vol.mountFlags, vol.mountUserId, new IVoldMountCallback.Stub() { @Override - public boolean onVolumeChecking(FileDescriptor deviceFd, String path, + public boolean onVolumeChecking(FileDescriptor fd, String path, String internalPath) { vol.path = path; vol.internalPath = internalPath; + ParcelFileDescriptor pfd = new ParcelFileDescriptor(fd); try { - mStorageSessionController.onVolumeMount(deviceFd, vol); + mStorageSessionController.onVolumeMount(pfd, vol); return true; } catch (ExternalStorageServiceException e) { - Slog.i(TAG, "Failed to mount volume " + vol, e); + Slog.e(TAG, "Failed to mount volume " + vol, e); + + Slog.i(TAG, "Scheduling reset in one minute"); + mHandler.removeMessages(H_RESET); + mHandler.sendMessageDelayed(mHandler.obtainMessage(H_RESET), + TimeUnit.SECONDS.toMillis(REMOTE_TIMEOUT_SECONDS * 2)); return false; + } finally { + try { + pfd.close(); + } catch (Exception e) { + Slog.e(TAG, "Failed to close FUSE device fd", e); + } } } }); @@ -2855,8 +2868,10 @@ class StorageManagerService extends IStorageManager.Stub */ @Override public void startCheckpoint(int numTries) throws RemoteException { - // Only the system process is permitted to start checkpoints - if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) { + // Only the root, system_server and shell processes are permitted to start checkpoints + final int callingUid = Binder.getCallingUid(); + if (callingUid != Process.SYSTEM_UID && callingUid != Process.ROOT_UID + && callingUid != Process.SHELL_UID) { throw new SecurityException("no permission to start filesystem checkpoint"); } diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java index f46b9ae3e8fb..b1584fea90c1 100644 --- a/services/core/java/com/android/server/SystemService.java +++ b/services/core/java/com/android/server/SystemService.java @@ -21,6 +21,9 @@ import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_DEFAULT; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.annotation.SystemApi.Client; +import android.annotation.SystemApi.Process; import android.annotation.UserIdInt; import android.app.ActivityThread; import android.content.Context; @@ -62,7 +65,7 @@ import java.util.List; * * {@hide} */ -//@SystemApi(client = Client.MODULE_LIBRARIES, process = Process.SYSTEM_SERVER) +@SystemApi(client = Client.MODULE_LIBRARIES, process = Process.SYSTEM_SERVER) public abstract class SystemService { /** @hide */ @@ -129,7 +132,7 @@ public abstract class SystemService { * Class representing user in question in the lifecycle callbacks. * @hide */ - //@SystemApi(client = Client.MODULE_LIBRARIES, process = Process.SYSTEM_SERVER) + @SystemApi(client = Client.MODULE_LIBRARIES, process = Process.SYSTEM_SERVER) public static final class TargetUser { @NonNull private final UserInfo mUserInfo; diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java index c0f43a81eca4..e7f78462e6ee 100644 --- a/services/core/java/com/android/server/SystemServiceManager.java +++ b/services/core/java/com/android/server/SystemServiceManager.java @@ -25,11 +25,14 @@ import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; import android.os.UserManagerInternal; +import android.util.ArrayMap; import android.util.Slog; import com.android.server.SystemService.TargetUser; import com.android.server.utils.TimingsTraceAndSlog; +import dalvik.system.PathClassLoader; + import java.io.File; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; @@ -63,6 +66,9 @@ public class SystemServiceManager { // Services that should receive lifecycle events. private final ArrayList<SystemService> mServices = new ArrayList<SystemService>(); + // Map of paths to PathClassLoader, so we don't load the same path multiple times. + private final ArrayMap<String, PathClassLoader> mLoadedPaths = new ArrayMap<>(); + private int mCurrentPhase = -1; private UserManagerInternal mUserManagerInternal; @@ -76,20 +82,46 @@ public class SystemServiceManager { * * @return The service instance. */ - @SuppressWarnings("unchecked") public SystemService startService(String className) { - final Class<SystemService> serviceClass; + final Class<SystemService> serviceClass = loadClassFromLoader(className, + this.getClass().getClassLoader()); + return startService(serviceClass); + } + + /** + * Starts a service by class name and a path that specifies the jar where the service lives. + * + * @return The service instance. + */ + public SystemService startServiceFromJar(String className, String path) { + PathClassLoader pathClassLoader = mLoadedPaths.get(path); + if (pathClassLoader == null) { + // NB: the parent class loader should always be the system server class loader. + // Changing it has implications that require discussion with the mainline team. + pathClassLoader = new PathClassLoader(path, this.getClass().getClassLoader()); + mLoadedPaths.put(path, pathClassLoader); + } + final Class<SystemService> serviceClass = loadClassFromLoader(className, pathClassLoader); + return startService(serviceClass); + } + + /* + * Loads and initializes a class from the given classLoader. Returns the class. + */ + @SuppressWarnings("unchecked") + private static Class<SystemService> loadClassFromLoader(String className, + ClassLoader classLoader) { try { - serviceClass = (Class<SystemService>)Class.forName(className); + return (Class<SystemService>) Class.forName(className, true, classLoader); } catch (ClassNotFoundException ex) { - Slog.i(TAG, "Starting " + className); throw new RuntimeException("Failed to create service " + className - + ": service class not found, usually indicates that the caller should " + + " from class loader " + classLoader.toString() + ": service class not " + + "found, usually indicates that the caller should " + "have called PackageManager.hasSystemFeature() to check whether the " + "feature is available on this device before trying to start the " - + "services that implement it", ex); + + "services that implement it. Also ensure that the correct path for the " + + "classloader is supplied, if applicable.", ex); } - return startService(serviceClass); } /** diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 2b7745b7428a..4f03a8e878e1 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -46,6 +46,7 @@ import android.telephony.Annotation.ApnType; import android.telephony.Annotation.DataFailureCause; import android.telephony.Annotation.RadioPowerState; import android.telephony.Annotation.SrvccState; +import android.telephony.BarringInfo; import android.telephony.CallAttributes; import android.telephony.CallQuality; import android.telephony.CellIdentity; @@ -254,6 +255,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { private int[] mCallPreciseDisconnectCause; + private List<BarringInfo> mBarringInfo = null; + private boolean mCarrierNetworkChangeState = false; private PhoneCapability mPhoneCapability = null; @@ -436,6 +439,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { cutListToSize(mCellInfo, mNumPhones); cutListToSize(mImsReasonInfo, mNumPhones); cutListToSize(mPreciseDataConnectionStates, mNumPhones); + cutListToSize(mBarringInfo, mNumPhones); return; } @@ -467,6 +471,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mForegroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE; mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE; mPreciseDataConnectionStates.add(new HashMap<Integer, PreciseDataConnectionState>()); + mBarringInfo.add(i, new BarringInfo()); } } @@ -524,6 +529,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mEmergencyNumberList = new HashMap<>(); mOutgoingCallEmergencyNumber = new EmergencyNumber[numPhones]; mOutgoingSmsEmergencyNumber = new EmergencyNumber[numPhones]; + mBarringInfo = new ArrayList<>(); for (int i = 0; i < numPhones; i++) { mCallState[i] = TelephonyManager.CALL_STATE_IDLE; mDataActivity[i] = TelephonyManager.DATA_ACTIVITY_NONE; @@ -551,6 +557,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mForegroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE; mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE; mPreciseDataConnectionStates.add(new HashMap<Integer, PreciseDataConnectionState>()); + mBarringInfo.add(i, new BarringInfo()); } mAppOps = mContext.getSystemService(AppOpsManager.class); @@ -993,6 +1000,19 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { remove(r.binder); } } + if ((events & PhoneStateListener.LISTEN_BARRING_INFO) != 0) { + BarringInfo barringInfo = mBarringInfo.get(phoneId); + BarringInfo biNoLocation = barringInfo != null + ? barringInfo.createLocationInfoSanitizedCopy() : null; + if (VDBG) log("listen: call onBarringInfoChanged=" + barringInfo); + try { + r.callback.onBarringInfoChanged( + checkFineLocationAccess(r, Build.VERSION_CODES.R) + ? barringInfo : biNoLocation); + } catch (RemoteException ex) { + remove(r.binder); + } + } } } } else { @@ -2102,6 +2122,52 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } + /** + * Send a notification of changes to barring status to PhoneStateListener registrants. + * + * @param phoneId the phoneId + * @param subId the subId + * @param barringInfo a structure containing the complete updated barring info. + */ + public void notifyBarringInfoChanged(int phoneId, int subId, @NonNull BarringInfo barringInfo) { + if (!checkNotifyPermission("notifyBarringInfo()")) { + return; + } + if (barringInfo == null) { + log("Received null BarringInfo for subId=" + subId + ", phoneId=" + phoneId); + mBarringInfo.set(phoneId, new BarringInfo()); + return; + } + + synchronized (mRecords) { + if (validatePhoneId(phoneId)) { + mBarringInfo.set(phoneId, barringInfo); + // Barring info is non-null + BarringInfo biNoLocation = barringInfo.createLocationInfoSanitizedCopy(); + if (VDBG) log("listen: call onBarringInfoChanged=" + barringInfo); + for (Record r : mRecords) { + if (r.matchPhoneStateListenerEvent( + PhoneStateListener.LISTEN_BARRING_INFO) + && idMatch(r.subId, subId, phoneId)) { + try { + if (DBG_LOC) { + log("notifyBarringInfo: mBarringInfo=" + + barringInfo + " r=" + r); + } + r.callback.onBarringInfoChanged( + checkFineLocationAccess(r, Build.VERSION_CODES.R) + ? barringInfo : biNoLocation); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } + } + } + } + handleRemoveListLocked(); + } + } + + @Override public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); @@ -2142,6 +2208,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { pw.println("mPreciseDataConnectionStates=" + mPreciseDataConnectionStates.get(i)); pw.println("mOutgoingCallEmergencyNumber=" + mOutgoingCallEmergencyNumber[i]); pw.println("mOutgoingSmsEmergencyNumber=" + mOutgoingSmsEmergencyNumber[i]); + pw.println("mBarringInfo=" + mBarringInfo.get(i)); pw.decreaseIndent(); } pw.println("mCarrierNetworkChangeState=" + mCarrierNetworkChangeState); diff --git a/services/core/java/com/android/server/TestNetworkService.java b/services/core/java/com/android/server/TestNetworkService.java index c27b0da780b7..ed3bab97ca19 100644 --- a/services/core/java/com/android/server/TestNetworkService.java +++ b/services/core/java/com/android/server/TestNetworkService.java @@ -218,7 +218,7 @@ class TestNetworkService extends ITestNetworkManager.Stub { // Has to be in TestNetworkAgent to ensure all teardown codepaths properly clean up // resources, even for binder death or unwanted calls. synchronized (mTestNetworkTracker) { - mTestNetworkTracker.remove(netId); + mTestNetworkTracker.remove(network.netId); } } } @@ -337,7 +337,7 @@ class TestNetworkService extends ITestNetworkManager.Stub { callingUid, binder); - mTestNetworkTracker.put(agent.netId, agent); + mTestNetworkTracker.put(agent.network.netId, agent); } } catch (SocketException e) { throw new UncheckedIOException(e); diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java index 27e0d52d78ee..5a56a9fa5367 100644 --- a/services/core/java/com/android/server/VibratorService.java +++ b/services/core/java/com/android/server/VibratorService.java @@ -61,7 +61,6 @@ import android.provider.DeviceConfig; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.util.DebugUtils; -import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.StatsLog; @@ -164,8 +163,7 @@ public class VibratorService extends IVibratorService.Stub private int mHapticFeedbackIntensity; private int mNotificationIntensity; private int mRingIntensity; - private SparseArray<Pair<VibrationEffect, VibrationAttributes>> mAlwaysOnEffects = - new SparseArray<>(); + private SparseArray<Vibration> mAlwaysOnEffects = new SparseArray<>(); static native boolean vibratorExists(); static native void vibratorInit(); @@ -461,6 +459,10 @@ public class VibratorService extends IVibratorService.Stub Settings.System.getUriFor(Settings.System.RING_VIBRATION_INTENSITY), true, mSettingObserver, UserHandle.USER_ALL); + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(Settings.Global.ZEN_MODE), + true, mSettingObserver, UserHandle.USER_ALL); + mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -508,7 +510,8 @@ public class VibratorService extends IVibratorService.Stub } @Override // Binder call - public boolean setAlwaysOnEffect(int id, VibrationEffect effect, VibrationAttributes attrs) { + public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, VibrationEffect effect, + VibrationAttributes attrs) { if (!hasPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)) { throw new SecurityException("Requires VIBRATE_ALWAYS_ON permission"); } @@ -518,8 +521,8 @@ public class VibratorService extends IVibratorService.Stub } if (effect == null) { synchronized (mLock) { - mAlwaysOnEffects.delete(id); - vibratorAlwaysOnDisable(id); + mAlwaysOnEffects.delete(alwaysOnId); + vibratorAlwaysOnDisable(alwaysOnId); } } else { if (!verifyVibrationEffect(effect)) { @@ -529,13 +532,11 @@ public class VibratorService extends IVibratorService.Stub Slog.e(TAG, "Only prebaked effects supported for always-on."); return false; } - if (attrs == null) { - attrs = new VibrationAttributes.Builder() - .build(); - } + attrs = fixupVibrationAttributes(attrs); synchronized (mLock) { - mAlwaysOnEffects.put(id, Pair.create(effect, attrs)); - updateAlwaysOnLocked(id, effect, attrs); + Vibration vib = new Vibration(null, effect, attrs, uid, opPkg, null); + mAlwaysOnEffects.put(alwaysOnId, vib); + updateAlwaysOnLocked(alwaysOnId, vib); } } return true; @@ -575,6 +576,23 @@ public class VibratorService extends IVibratorService.Stub return true; } + private VibrationAttributes fixupVibrationAttributes(VibrationAttributes attrs) { + if (attrs == null) { + attrs = DEFAULT_ATTRIBUTES; + } + if (shouldBypassDnd(attrs)) { + if (!(hasPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) + || hasPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + || hasPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING))) { + final int flags = attrs.getFlags() + & ~VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY; + attrs = new VibrationAttributes.Builder(attrs).replaceFlags(flags).build(); + } + } + + return attrs; + } + private static long[] getLongIntArray(Resources r, int resid) { int[] ar = r.getIntArray(resid); if (ar == null) { @@ -604,19 +622,7 @@ public class VibratorService extends IVibratorService.Stub return; } - if (attrs == null) { - attrs = DEFAULT_ATTRIBUTES; - } - - if (shouldBypassDnd(attrs)) { - if (!(hasPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) - || hasPermission(android.Manifest.permission.MODIFY_PHONE_STATE) - || hasPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING))) { - final int flags = attrs.getFlags() - & ~VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY; - attrs = new VibrationAttributes.Builder(attrs).replaceFlags(flags).build(); - } - } + attrs = fixupVibrationAttributes(attrs); // If our current vibration is longer than the new vibration and is the same amplitude, // then just let the current one finish. @@ -777,29 +783,8 @@ public class VibratorService extends IVibratorService.Stub private void startVibrationLocked(final Vibration vib) { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked"); try { - if (!isAllowedToVibrateLocked(vib)) { - return; - } - final int intensity = getCurrentIntensityLocked(vib); - if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) { - return; - } - - if (vib.isRingtone() && !shouldVibrateForRingtone()) { - if (DEBUG) { - Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones"); - } - return; - } - - final int mode = getAppOpMode(vib); - if (mode != AppOpsManager.MODE_ALLOWED) { - if (mode == AppOpsManager.MODE_ERRORED) { - // We might be getting calls from within system_server, so we don't actually - // want to throw a SecurityException here. - Slog.w(TAG, "Would be an error: vibrate from uid " + vib.uid); - } + if (!shouldVibrate(vib, intensity)) { return; } applyVibrationIntensityScalingLocked(vib, intensity); @@ -958,6 +943,35 @@ public class VibratorService extends IVibratorService.Stub return mode; } + private boolean shouldVibrate(Vibration vib, int intensity) { + if (!isAllowedToVibrateLocked(vib)) { + return false; + } + + if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) { + return false; + } + + if (vib.isRingtone() && !shouldVibrateForRingtone()) { + if (DEBUG) { + Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones"); + } + return false; + } + + final int mode = getAppOpMode(vib); + if (mode != AppOpsManager.MODE_ALLOWED) { + if (mode == AppOpsManager.MODE_ERRORED) { + // We might be getting calls from within system_server, so we don't actually + // want to throw a SecurityException here. + Slog.w(TAG, "Would be an error: vibrate from uid " + vib.uid); + } + return false; + } + + return true; + } + @GuardedBy("mLock") private void reportFinishVibrationLocked() { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked"); @@ -1069,14 +1083,12 @@ public class VibratorService extends IVibratorService.Stub mVibrator.getDefaultRingVibrationIntensity(), UserHandle.USER_CURRENT); } - private void updateAlwaysOnLocked(int id, VibrationEffect effect, VibrationAttributes attrs) { - // TODO: Check DND and LowPower settings - final Vibration vib = new Vibration(null, effect, attrs, 0, null, null); + private void updateAlwaysOnLocked(int id, Vibration vib) { final int intensity = getCurrentIntensityLocked(vib); - if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) { + if (!shouldVibrate(vib, intensity)) { vibratorAlwaysOnDisable(id); } else { - final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect; + final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.effect; final int strength = intensityToEffectStrength(intensity); vibratorAlwaysOnEnable(id, prebaked.getId(), strength); } @@ -1085,8 +1097,8 @@ public class VibratorService extends IVibratorService.Stub private void updateAlwaysOnLocked() { for (int i = 0; i < mAlwaysOnEffects.size(); i++) { int id = mAlwaysOnEffects.keyAt(i); - Pair<VibrationEffect, VibrationAttributes> pair = mAlwaysOnEffects.valueAt(i); - updateAlwaysOnLocked(id, pair.first, pair.second); + Vibration vib = mAlwaysOnEffects.valueAt(i); + updateAlwaysOnLocked(id, vib); } } diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index a372fca07728..debc2a116934 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -2129,16 +2129,6 @@ public class AccountManagerService } @Override - public void removeAccount(IAccountManagerResponse response, Account account, - boolean expectActivityLaunch) { - removeAccountAsUser( - response, - account, - expectActivityLaunch, - UserHandle.getCallingUserId()); - } - - @Override public void removeAccountAsUser(IAccountManagerResponse response, Account account, boolean expectActivityLaunch, int userId) { final int callingUid = Binder.getCallingUid(); @@ -4454,12 +4444,6 @@ public class AccountManagerService @Override @NonNull - public Account[] getAccounts(String type, String opPackageName) { - return getAccountsAsUser(type, UserHandle.getCallingUserId(), opPackageName); - } - - @Override - @NonNull public Account[] getAccountsForPackage(String packageName, int uid, String opPackageName) { int callingUid = Binder.getCallingUid(); if (!UserHandle.isSameApp(callingUid, Process.SYSTEM_UID)) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index c21adb08270e..883e7c6799dc 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2555,7 +2555,7 @@ public class ActivityManagerService extends IActivityManager.Stub Process.setThreadGroupAndCpuset(BackgroundThread.get().getThreadId(), Process.THREAD_GROUP_SYSTEM); Process.setThreadGroupAndCpuset( - mOomAdjuster.mAppCompact.mCompactionThread.getThreadId(), + mOomAdjuster.mCachedAppOptimizer.mCachedAppOptimizerThread.getThreadId(), Process.THREAD_GROUP_SYSTEM); } catch (Exception e) { Slog.w(TAG, "Setting background thread cpuset failed"); @@ -5304,7 +5304,7 @@ public class ActivityManagerService extends IActivityManager.Stub String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) { synchronized (ActivityManagerService.this) { - mOomAdjuster.mAppCompact.compactAllSystem(); + mOomAdjuster.mCachedAppOptimizer.compactAllSystem(); requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false); } } @@ -9000,7 +9000,7 @@ public class ActivityManagerService extends IActivityManager.Stub final long timeSinceLastIdle = now - mLastIdleTime; // Compact all non-zygote processes to freshen up the page cache. - mOomAdjuster.mAppCompact.compactAllSystem(); + mOomAdjuster.mCachedAppOptimizer.compactAllSystem(); final long lowRamSinceLastIdle = getLowRamTimeSinceIdle(now); mLastIdleTime = now; @@ -9108,13 +9108,14 @@ public class ActivityManagerService extends IActivityManager.Stub final Resources res = mContext.getResources(); mAppErrors.loadAppsNotReportingCrashesFromConfigLocked(res.getString( com.android.internal.R.string.config_appsNotReportingCrashes)); - mUserController.mUserSwitchUiEnabled = !res.getBoolean( + final boolean userSwitchUiEnabled = !res.getBoolean( com.android.internal.R.bool.config_customUserSwitchUi); - mUserController.mMaxRunningUsers = res.getInteger( + final int maxRunningUsers = res.getInteger( com.android.internal.R.integer.config_multiuserMaxRunningUsers); - mUserController.mDelayUserDataLocking = res.getBoolean( + final boolean delayUserDataLocking = res.getBoolean( com.android.internal.R.bool.config_multiuserDelayUserDataLocking); - + mUserController.setInitialConfig(userSwitchUiEnabled, maxRunningUsers, + delayUserDataLocking); mWaitForNetworkTimeoutMs = waitForNetworkTimeoutMs; mPssDeferralTime = pssDeferralMs; } @@ -10020,7 +10021,7 @@ public class ActivityManagerService extends IActivityManager.Stub synchronized(this) { mConstants.dump(pw); - mOomAdjuster.dumpAppCompactorSettings(pw); + mOomAdjuster.dumpCachedAppOptimizerSettings(pw); pw.println(); if (dumpAll) { pw.println("-------------------------------------------------------------------------------"); @@ -10425,7 +10426,7 @@ public class ActivityManagerService extends IActivityManager.Stub } else if ("settings".equals(cmd)) { synchronized (this) { mConstants.dump(pw); - mOomAdjuster.dumpAppCompactorSettings(pw); + mOomAdjuster.dumpCachedAppOptimizerSettings(pw); } } else if ("services".equals(cmd) || "s".equals(cmd)) { if (dumpClient) { @@ -16408,6 +16409,22 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override + public boolean updateMccMncConfiguration(String mcc, String mnc) { + int mccInt, mncInt; + try { + mccInt = Integer.parseInt(mcc); + mncInt = Integer.parseInt(mnc); + } catch (NumberFormatException | StringIndexOutOfBoundsException ex) { + Slog.e(TAG, "Error parsing mcc: " + mcc + " mnc: " + mnc + ". ex=" + ex); + return false; + } + Configuration config = new Configuration(); + config.mcc = mccInt; + config.mnc = mncInt == 0 ? Configuration.MNC_ZERO : mncInt; + return mActivityTaskManager.updateConfiguration(config); + } + + @Override public int getLaunchedFromUid(IBinder activityToken) { return mActivityTaskManager.getLaunchedFromUid(activityToken); } @@ -17926,7 +17943,31 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public int stopUser(final int userId, boolean force, final IStopUserCallback callback) { - return mUserController.stopUser(userId, force, callback, null /* keyEvictedCallback */); + return mUserController.stopUser(userId, force, /* allowDelayedLocking= */ false, + /* callback= */ callback, /* keyEvictedCallback= */ null); + } + + /** + * Stops user but allow delayed locking. Delayed locking keeps user unlocked even after + * stopping only if {@code config_multiuserDelayUserDataLocking} overlay is set true. + * + * <p>When delayed locking is not enabled through the overlay, this call becomes the same + * with {@link #stopUser(int, boolean, IStopUserCallback)} call. + * + * @param userId User id to stop. + * @param force Force stop the user even if the user is related with system user or current + * user. + * @param callback Callback called when user has stopped. + * + * @return {@link ActivityManager#USER_OP_SUCCESS} when user is stopped successfully. Returns + * other {@code ActivityManager#USER_OP_*} codes for failure. + * + */ + @Override + public int stopUserWithDelayedLocking(final int userId, boolean force, + final IStopUserCallback callback) { + return mUserController.stopUser(userId, force, /* allowDelayedLocking= */ true, + /* callback= */ callback, /* keyEvictedCallback= */ null); } @Override @@ -18332,7 +18373,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public int getMaxRunningUsers() { - return mUserController.mMaxRunningUsers; + return mUserController.getMaxRunningUsers(); } @Override diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 37026fd42f23..a98b83b09a6a 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -402,7 +402,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub } public ParcelFileDescriptor getStatisticsStream() { - mContext.enforceCallingPermission( + mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BATTERY_STATS, null); //Slog.i("foo", "SENDING BATTERY INFO:"); //mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM)); diff --git a/services/core/java/com/android/server/am/AppCompactor.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index b7e206516640..3ca5ebce93f2 100644 --- a/services/core/java/com/android/server/am/AppCompactor.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -51,7 +51,7 @@ import java.util.Map; import java.util.Random; import java.util.Set; -public final class AppCompactor { +public final class CachedAppOptimizer { // Flags stored in the DeviceConfig API. @VisibleForTesting static final String KEY_USE_COMPACTION = "use_compaction"; @@ -122,7 +122,7 @@ public final class AppCompactor { * that will wipe out the cpuset assignment for system_server threads. * Accordingly, this is in the AMS constructor. */ - final ServiceThread mCompactionThread; + final ServiceThread mCachedAppOptimizerThread; private final ArrayList<ProcessRecord> mPendingCompactionProcesses = new ArrayList<ProcessRecord>(); @@ -214,15 +214,15 @@ public final class AppCompactor { private int mPersistentCompactionCount; private int mBfgsCompactionCount; - public AppCompactor(ActivityManagerService am) { + public CachedAppOptimizer(ActivityManagerService am) { mAm = am; - mCompactionThread = new ServiceThread("CompactionThread", + mCachedAppOptimizerThread = new ServiceThread("CachedAppOptimizerThread", THREAD_PRIORITY_FOREGROUND, true); mProcStateThrottle = new HashSet<>(); } @VisibleForTesting - AppCompactor(ActivityManagerService am, PropertyChangedCallbackForTest callback) { + CachedAppOptimizer(ActivityManagerService am, PropertyChangedCallbackForTest callback) { this(am); mTestCallback = callback; } @@ -243,7 +243,7 @@ public final class AppCompactor { updateFullDeltaRssThrottle(); updateProcStateThrottle(); } - Process.setThreadGroupAndCpuset(mCompactionThread.getThreadId(), + Process.setThreadGroupAndCpuset(mCachedAppOptimizerThread.getThreadId(), Process.THREAD_GROUP_SYSTEM); } @@ -258,7 +258,7 @@ public final class AppCompactor { @GuardedBy("mAm") void dump(PrintWriter pw) { - pw.println("AppCompactor settings"); + pw.println("CachedAppOptimizer settings"); synchronized (mPhenotypeFlagLock) { pw.println(" " + KEY_USE_COMPACTION + "=" + mUseCompaction); pw.println(" " + KEY_COMPACT_ACTION_1 + "=" + mCompactActionSome); @@ -300,7 +300,7 @@ public final class AppCompactor { app.reqCompactAction = COMPACT_PROCESS_SOME; mPendingCompactionProcesses.add(app); mCompactionHandler.sendMessage( - mCompactionHandler.obtainMessage( + mCompactionHandler.obtainMessage( COMPACT_PROCESS_MSG, app.setAdj, app.setProcState)); } @@ -309,7 +309,7 @@ public final class AppCompactor { app.reqCompactAction = COMPACT_PROCESS_FULL; mPendingCompactionProcesses.add(app); mCompactionHandler.sendMessage( - mCompactionHandler.obtainMessage( + mCompactionHandler.obtainMessage( COMPACT_PROCESS_MSG, app.setAdj, app.setProcState)); } @@ -362,8 +362,8 @@ public final class AppCompactor { private void updateUseCompaction() { mUseCompaction = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_USE_COMPACTION, DEFAULT_USE_COMPACTION); - if (mUseCompaction && !mCompactionThread.isAlive()) { - mCompactionThread.start(); + if (mUseCompaction && !mCachedAppOptimizerThread.isAlive()) { + mCachedAppOptimizerThread.start(); mCompactionHandler = new MemCompactionHandler(); } } @@ -521,7 +521,7 @@ public final class AppCompactor { private final class MemCompactionHandler extends Handler { private MemCompactionHandler() { - super(mCompactionThread.getLooper()); + super(mCachedAppOptimizerThread.getLooper()); } @Override diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 0fc885a2e61f..f86d6a70a076 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -122,9 +122,9 @@ public final class OomAdjuster { PowerManagerInternal mLocalPowerManager; /** - * Service for compacting background apps. + * Service for optimizing resource usage from background apps. */ - AppCompactor mAppCompact; + CachedAppOptimizer mCachedAppOptimizer; ActivityManagerConstants mConstants; @@ -197,7 +197,7 @@ public final class OomAdjuster { mLocalPowerManager = LocalServices.getService(PowerManagerInternal.class); mConstants = mService.mConstants; - mAppCompact = new AppCompactor(mService); + mCachedAppOptimizer = new CachedAppOptimizer(mService); mProcessGroupHandler = new Handler(adjusterThread.getLooper(), msg -> { final int pid = msg.arg1; @@ -224,7 +224,7 @@ public final class OomAdjuster { } void initSettings() { - mAppCompact.init(); + mCachedAppOptimizer.init(); } /** @@ -1978,7 +1978,7 @@ public final class OomAdjuster { int changes = 0; // don't compact during bootup - if (mAppCompact.useCompaction() && mService.mBooted) { + if (mCachedAppOptimizer.useCompaction() && mService.mBooted) { // Cached and prev/home compaction if (app.curAdj != app.setAdj) { // Perform a minor compaction when a perceptible app becomes the prev/home app @@ -1987,26 +1987,26 @@ public final class OomAdjuster { if (app.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ && (app.curAdj == ProcessList.PREVIOUS_APP_ADJ || app.curAdj == ProcessList.HOME_APP_ADJ)) { - mAppCompact.compactAppSome(app); + mCachedAppOptimizer.compactAppSome(app); } else if ((app.setAdj < ProcessList.CACHED_APP_MIN_ADJ || app.setAdj > ProcessList.CACHED_APP_MAX_ADJ) && app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ && app.curAdj <= ProcessList.CACHED_APP_MAX_ADJ) { - mAppCompact.compactAppFull(app); + mCachedAppOptimizer.compactAppFull(app); } } else if (mService.mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE && app.setAdj < ProcessList.FOREGROUND_APP_ADJ // Because these can fire independent of oom_adj/procstate changes, we need // to throttle the actual dispatch of these requests in addition to the // processing of the requests. As a result, there is throttling both here - // and in AppCompactor. - && mAppCompact.shouldCompactPersistent(app, now)) { - mAppCompact.compactAppPersistent(app); + // and in CachedAppOptimizer. + && mCachedAppOptimizer.shouldCompactPersistent(app, now)) { + mCachedAppOptimizer.compactAppPersistent(app); } else if (mService.mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE && app.getCurProcState() == ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE - && mAppCompact.shouldCompactBFGS(app, now)) { - mAppCompact.compactAppBfgs(app); + && mCachedAppOptimizer.shouldCompactBFGS(app, now)) { + mCachedAppOptimizer.compactAppBfgs(app); } } @@ -2439,7 +2439,7 @@ public final class OomAdjuster { } @GuardedBy("mService") - void dumpAppCompactorSettings(PrintWriter pw) { - mAppCompact.dump(pw); + void dumpCachedAppOptimizerSettings(PrintWriter pw) { + mCachedAppOptimizer.dump(pw); } } diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index e11008c246dd..b7f867df04c2 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -647,11 +647,10 @@ public final class ProcessList { // Get this after boot, and won't be changed until it's rebooted, as we don't // want some apps enabled while some apps disabled mAppDataIsolationEnabled = - SystemProperties.getBoolean(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, false); + SystemProperties.getBoolean(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, true); mAppDataIsolationWhitelistedApps = new ArrayList<>( SystemConfig.getInstance().getAppDataIsolationWhitelistedApps()); - if (sKillHandler == null) { sKillThread = new ServiceThread(TAG + ":kill", THREAD_PRIORITY_BACKGROUND, true /* allowIo */); diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 8ae18ff68b66..f3a2e70f9b89 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -93,7 +93,6 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.util.ArrayUtils; -import com.android.internal.util.Preconditions; import com.android.internal.widget.LockPatternUtils; import com.android.server.FgThread; import com.android.server.LocalServices; @@ -161,7 +160,8 @@ class UserController implements Handler.Callback { * <p>Note: Current and system user (and their related profiles) are never stopped when * switching users. Due to that, the actual number of running users can exceed mMaxRunningUsers */ - int mMaxRunningUsers; + @GuardedBy("mLock") + private int mMaxRunningUsers; // Lock for internal state. private final Object mLock = new Object(); @@ -213,7 +213,8 @@ class UserController implements Handler.Callback { private final RemoteCallbackList<IUserSwitchObserver> mUserSwitchObservers = new RemoteCallbackList<>(); - boolean mUserSwitchUiEnabled = true; + @GuardedBy("mLock") + private boolean mUserSwitchUiEnabled = true; /** * Currently active user switch callbacks. @@ -246,10 +247,11 @@ class UserController implements Handler.Callback { /** * In this mode, user is always stopped when switched out but locking of user data is * postponed until total number of unlocked users in the system reaches mMaxRunningUsers. - * Once total number of unlocked users reach mMaxRunningUsers, least recentely used user + * Once total number of unlocked users reach mMaxRunningUsers, least recently used user * will be locked. */ - boolean mDelayUserDataLocking; + @GuardedBy("mLock") + private boolean mDelayUserDataLocking; /** * Keep track of last active users for mDelayUserDataLocking. * The latest stopped user is placed in front while the least recently stopped user in back. @@ -275,6 +277,33 @@ class UserController implements Handler.Callback { updateStartedUserArrayLU(); } + void setInitialConfig(boolean userSwitchUiEnabled, int maxRunningUsers, + boolean delayUserDataLocking) { + synchronized (mLock) { + mUserSwitchUiEnabled = userSwitchUiEnabled; + mMaxRunningUsers = maxRunningUsers; + mDelayUserDataLocking = delayUserDataLocking; + } + } + + private boolean isUserSwitchUiEnabled() { + synchronized (mLock) { + return mUserSwitchUiEnabled; + } + } + + int getMaxRunningUsers() { + synchronized (mLock) { + return mMaxRunningUsers; + } + } + + private boolean isDelayUserDataLockingEnabled() { + synchronized (mLock) { + return mDelayUserDataLocking; + } + } + void finishUserSwitch(UserState uss) { // This call holds the AM lock so we post to the handler. mHandler.post(() -> { @@ -321,7 +350,11 @@ class UserController implements Handler.Callback { // Owner/System user and current user can't be stopped continue; } - if (stopUsersLU(userId, false, null, null) == USER_OP_SUCCESS) { + // allowDelayedLocking set here as stopping user is done without any explicit request + // from outside. + if (stopUsersLU(userId, /* force= */ false, /* allowDelayedLocking= */ true, + /* stopUserCallback= */ null, /* keyEvictedCallback= */ null) + == USER_OP_SUCCESS) { iterator.remove(); } } @@ -567,8 +600,8 @@ class UserController implements Handler.Callback { // intialize it; it should be stopped right away as it's not really a "real" user. // TODO(b/143092698): in the long-term, it might be better to add a onCreateUser() // callback on SystemService instead. - stopUser(userInfo.id, /* force= */ true, /* stopUserCallback= */ null, - /* keyEvictedCallback= */ null); + stopUser(userInfo.id, /* force= */ true, /* allowDelayedLocking= */ false, + /* stopUserCallback= */ null, /* keyEvictedCallback= */ null); return; } @@ -611,7 +644,8 @@ class UserController implements Handler.Callback { } int restartUser(final int userId, final boolean foreground) { - return stopUser(userId, /* force */ true, null, new KeyEvictedCallback() { + return stopUser(userId, /* force= */ true, /* allowDelayedLocking= */ false, + /* stopUserCallback= */ null, new KeyEvictedCallback() { @Override public void keyEvicted(@UserIdInt int userId) { // Post to the same handler that this callback is called from to ensure the user @@ -621,15 +655,16 @@ class UserController implements Handler.Callback { }); } - int stopUser(final int userId, final boolean force, final IStopUserCallback stopUserCallback, - KeyEvictedCallback keyEvictedCallback) { + int stopUser(final int userId, final boolean force, boolean allowDelayedLocking, + final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) { checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "stopUser"); if (userId < 0 || userId == UserHandle.USER_SYSTEM) { throw new IllegalArgumentException("Can't stop system user " + userId); } enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId); synchronized (mLock) { - return stopUsersLU(userId, force, stopUserCallback, keyEvictedCallback); + return stopUsersLU(userId, force, allowDelayedLocking, stopUserCallback, + keyEvictedCallback); } } @@ -638,7 +673,7 @@ class UserController implements Handler.Callback { * {@link #getUsersToStopLU(int)} to determine the list of users that should be stopped. */ @GuardedBy("mLock") - private int stopUsersLU(final int userId, boolean force, + private int stopUsersLU(final int userId, boolean force, boolean allowDelayedLocking, final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) { if (userId == UserHandle.USER_SYSTEM) { return USER_OP_ERROR_IS_SYSTEM; @@ -657,7 +692,8 @@ class UserController implements Handler.Callback { if (force) { Slog.i(TAG, "Force stop user " + userId + ". Related users will not be stopped"); - stopSingleUserLU(userId, stopUserCallback, keyEvictedCallback); + stopSingleUserLU(userId, allowDelayedLocking, stopUserCallback, + keyEvictedCallback); return USER_OP_SUCCESS; } return USER_OP_ERROR_RELATED_USERS_CANNOT_STOP; @@ -665,21 +701,64 @@ class UserController implements Handler.Callback { } if (DEBUG_MU) Slog.i(TAG, "stopUsersLocked usersToStop=" + Arrays.toString(usersToStop)); for (int userIdToStop : usersToStop) { - stopSingleUserLU(userIdToStop, + stopSingleUserLU(userIdToStop, allowDelayedLocking, userIdToStop == userId ? stopUserCallback : null, userIdToStop == userId ? keyEvictedCallback : null); } return USER_OP_SUCCESS; } + /** + * Stops a single User. This can also trigger locking user data out depending on device's + * config ({@code mDelayUserDataLocking}) and arguments. + * User will be unlocked when + * - {@code mDelayUserDataLocking} is not set. + * - {@code mDelayUserDataLocking} is set and {@code keyEvictedCallback} is non-null. + * - + * + * @param userId User Id to stop and lock the data. + * @param allowDelayedLocking When set, do not lock user after stopping. Locking can happen + * later when number of unlocked users reaches + * {@code mMaxRunnngUsers}. Note that this is respected only when + * {@code mDelayUserDataLocking} is set and {@keyEvictedCallback} is + * null. Otherwise the user will be locked. + * @param stopUserCallback Callback to notify that user has stopped. + * @param keyEvictedCallback Callback to notify that user has been unlocked. + */ @GuardedBy("mLock") - private void stopSingleUserLU(final int userId, final IStopUserCallback stopUserCallback, + private void stopSingleUserLU(final int userId, boolean allowDelayedLocking, + final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) { if (DEBUG_MU) Slog.i(TAG, "stopSingleUserLocked userId=" + userId); final UserState uss = mStartedUsers.get(userId); - if (uss == null) { - // User is not started, nothing to do... but we do need to - // callback if requested. + if (uss == null) { // User is not started + // If mDelayUserDataLocking is set and allowDelayedLocking is not set, we need to lock + // the requested user as the client wants to stop and lock the user. On the other hand, + // having keyEvictedCallback set will lead into locking user if mDelayUserDataLocking + // is set as that means client wants to lock the user immediately. + // If mDelayUserDataLocking is not set, the user was already locked when it was stopped + // and no further action is necessary. + if (mDelayUserDataLocking) { + if (allowDelayedLocking && keyEvictedCallback != null) { + Slog.wtf(TAG, "allowDelayedLocking set with KeyEvictedCallback, ignore it" + + " and lock user:" + userId, new RuntimeException()); + allowDelayedLocking = false; + } + if (!allowDelayedLocking) { + if (mLastActiveUsers.remove(Integer.valueOf(userId))) { + // should lock the user, user is already gone + final ArrayList<KeyEvictedCallback> keyEvictedCallbacks; + if (keyEvictedCallback != null) { + keyEvictedCallbacks = new ArrayList<>(1); + keyEvictedCallbacks.add(keyEvictedCallback); + } else { + keyEvictedCallbacks = null; + } + dispatchUserLocking(userId, keyEvictedCallbacks); + } + } + } + // We do need to post the stopped callback even though user is already stopped. if (stopUserCallback != null) { mHandler.post(() -> { try { @@ -704,6 +783,7 @@ class UserController implements Handler.Callback { mInjector.getUserManagerInternal().setUserState(userId, uss.state); updateStartedUserArrayLU(); + final boolean allowDelayyLockingCopied = allowDelayedLocking; // Post to handler to obtain amLock mHandler.post(() -> { // We are going to broadcast ACTION_USER_STOPPING and then @@ -718,7 +798,8 @@ class UserController implements Handler.Callback { @Override public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) { - mHandler.post(() -> finishUserStopping(userId, uss)); + mHandler.post(() -> finishUserStopping(userId, uss, + allowDelayyLockingCopied)); } }; @@ -734,7 +815,8 @@ class UserController implements Handler.Callback { } } - void finishUserStopping(final int userId, final UserState uss) { + void finishUserStopping(final int userId, final UserState uss, + final boolean allowDelayedLocking) { Slog.d(TAG, "UserController event: finishUserStopping(" + userId + ")"); // On to the next. final Intent shutdownIntent = new Intent(Intent.ACTION_SHUTDOWN); @@ -746,7 +828,7 @@ class UserController implements Handler.Callback { mHandler.post(new Runnable() { @Override public void run() { - finishUserStopped(uss); + finishUserStopped(uss, allowDelayedLocking); } }); } @@ -773,7 +855,7 @@ class UserController implements Handler.Callback { Binder.getCallingPid(), userId); } - void finishUserStopped(UserState uss) { + void finishUserStopped(UserState uss, boolean allowDelayedLocking) { final int userId = uss.mHandle.getIdentifier(); Slog.d(TAG, "UserController event: finishUserStopped(" + userId + ")"); final boolean stopped; @@ -792,7 +874,13 @@ class UserController implements Handler.Callback { mStartedUsers.remove(userId); mUserLru.remove(Integer.valueOf(userId)); updateStartedUserArrayLU(); - userIdToLock = updateUserToLockLU(userId); + if (allowDelayedLocking && !keyEvictedCallbacks.isEmpty()) { + Slog.wtf(TAG, + "Delayed locking enabled while KeyEvictedCallbacks not empty, userId:" + + userId + " callbacks:" + keyEvictedCallbacks); + allowDelayedLocking = false; + } + userIdToLock = updateUserToLockLU(userId, allowDelayedLocking); if (userIdToLock == UserHandle.USER_NULL) { lockUser = false; } @@ -826,31 +914,36 @@ class UserController implements Handler.Callback { if (!lockUser) { return; } - final int userIdToLockF = userIdToLock; - // Evict the user's credential encryption key. Performed on FgThread to make it - // serialized with call to UserManagerService.onBeforeUnlockUser in finishUserUnlocking - // to prevent data corruption. - FgThread.getHandler().post(() -> { - synchronized (mLock) { - if (mStartedUsers.get(userIdToLockF) != null) { - Slog.w(TAG, "User was restarted, skipping key eviction"); - return; - } - } - try { - mInjector.getStorageManager().lockUserKey(userIdToLockF); - } catch (RemoteException re) { - throw re.rethrowAsRuntimeException(); - } - if (userIdToLockF == userId) { - for (final KeyEvictedCallback callback : keyEvictedCallbacks) { - callback.keyEvicted(userId); - } - } - }); + dispatchUserLocking(userIdToLock, keyEvictedCallbacks); } } + private void dispatchUserLocking(@UserIdInt int userId, + @Nullable List<KeyEvictedCallback> keyEvictedCallbacks) { + // Evict the user's credential encryption key. Performed on FgThread to make it + // serialized with call to UserManagerService.onBeforeUnlockUser in finishUserUnlocking + // to prevent data corruption. + FgThread.getHandler().post(() -> { + synchronized (mLock) { + if (mStartedUsers.get(userId) != null) { + Slog.w(TAG, "User was restarted, skipping key eviction"); + return; + } + } + try { + mInjector.getStorageManager().lockUserKey(userId); + } catch (RemoteException re) { + throw re.rethrowAsRuntimeException(); + } + if (keyEvictedCallbacks == null) { + return; + } + for (int i = 0; i < keyEvictedCallbacks.size(); i++) { + keyEvictedCallbacks.get(i).keyEvicted(userId); + } + }); + } + /** * For mDelayUserDataLocking mode, storage once unlocked is kept unlocked. * Total number of unlocked user storage is limited by mMaxRunningUsers. @@ -861,9 +954,9 @@ class UserController implements Handler.Callback { * @return user id to lock. UserHandler.USER_NULL will be returned if no user should be locked. */ @GuardedBy("mLock") - private int updateUserToLockLU(@UserIdInt int userId) { + private int updateUserToLockLU(@UserIdInt int userId, boolean allowDelayedLocking) { int userIdToLock = userId; - if (mDelayUserDataLocking && !getUserInfo(userId).isEphemeral() + if (mDelayUserDataLocking && allowDelayedLocking && !getUserInfo(userId).isEphemeral() && !hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, userId)) { mLastActiveUsers.remove((Integer) userId); // arg should be object, not index mLastActiveUsers.add(0, userId); @@ -945,7 +1038,8 @@ class UserController implements Handler.Callback { if (userInfo.isGuest() || userInfo.isEphemeral()) { // This is a user to be stopped. synchronized (mLock) { - stopUsersLU(oldUserId, true, null, null); + stopUsersLU(oldUserId, /* force= */ true, /* allowDelayedLocking= */ false, + null, null); } } } @@ -977,7 +1071,7 @@ class UserController implements Handler.Callback { } final int profilesToStartSize = profilesToStart.size(); int i = 0; - for (; i < profilesToStartSize && i < (mMaxRunningUsers - 1); ++i) { + for (; i < profilesToStartSize && i < (getMaxRunningUsers() - 1); ++i) { startUser(profilesToStart.get(i).id, /* foreground= */ false); } if (i < profilesToStartSize) { @@ -1094,7 +1188,7 @@ class UserController implements Handler.Callback { return false; } - if (foreground && mUserSwitchUiEnabled) { + if (foreground && isUserSwitchUiEnabled()) { t.traceBegin("startFreezingScreen"); mInjector.getWindowManager().startFreezingScreen( R.anim.screen_user_exit, R.anim.screen_user_enter); @@ -1142,9 +1236,11 @@ class UserController implements Handler.Callback { if (foreground) { // Make sure the old user is no longer considering the display to be on. mInjector.reportGlobalUsageEventLocked(UsageEvents.Event.SCREEN_NON_INTERACTIVE); + boolean userSwitchUiEnabled; synchronized (mLock) { mCurrentUserId = userId; mTargetUserId = UserHandle.USER_NULL; // reset, mCurrentUserId has caught up + userSwitchUiEnabled = mUserSwitchUiEnabled; } mInjector.updateUserConfiguration(); updateCurrentProfileIds(); @@ -1152,7 +1248,7 @@ class UserController implements Handler.Callback { mInjector.reportCurWakefulnessUsageEvent(); // Once the internal notion of the active user has switched, we lock the device // with the option to show the user switcher on the keyguard. - if (mUserSwitchUiEnabled) { + if (userSwitchUiEnabled) { mInjector.getWindowManager().setSwitchingUser(true); mInjector.getWindowManager().lockNow(null); } @@ -1391,10 +1487,12 @@ class UserController implements Handler.Callback { Slog.w(TAG, "Cannot switch to User #" + targetUserId + ": not a full user"); return false; } + boolean userSwitchUiEnabled; synchronized (mLock) { mTargetUserId = targetUserId; + userSwitchUiEnabled = mUserSwitchUiEnabled; } - if (mUserSwitchUiEnabled) { + if (userSwitchUiEnabled) { UserInfo currentUserInfo = getUserInfo(currentUserId); Pair<UserInfo, UserInfo> userNames = new Pair<>(currentUserInfo, targetUserInfo); mUiHandler.removeMessages(START_USER_SWITCH_UI_MSG); @@ -1458,14 +1556,15 @@ class UserController implements Handler.Callback { } // If running in background is disabled or mDelayUserDataLocking mode, stop the user. boolean disallowRunInBg = hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, - oldUserId) || mDelayUserDataLocking; + oldUserId) || isDelayUserDataLockingEnabled(); if (!disallowRunInBg) { return; } synchronized (mLock) { if (DEBUG_MU) Slog.i(TAG, "stopBackgroundUsersIfEnforced stopping " + oldUserId + " and related users"); - stopUsersLU(oldUserId, false, null, null); + stopUsersLU(oldUserId, /* force= */ false, /* allowDelayedLocking= */ true, + null, null); } } @@ -1551,7 +1650,7 @@ class UserController implements Handler.Callback { void continueUserSwitch(UserState uss, int oldUserId, int newUserId) { Slog.d(TAG, "Continue user switch oldUser #" + oldUserId + ", newUser #" + newUserId); - if (mUserSwitchUiEnabled) { + if (isUserSwitchUiEnabled()) { mInjector.getWindowManager().stopFreezingScreen(); } uss.switching = false; diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index cd2272aa421c..eedeeea5cdb3 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -2193,8 +2193,8 @@ public class AudioService extends IAudioService.Stub } private void enforceModifyAudioRoutingPermission() { - if (mContext.checkCallingPermission( - android.Manifest.permission.MODIFY_AUDIO_ROUTING) + if (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.MODIFY_AUDIO_ROUTING) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Missing MODIFY_AUDIO_ROUTING permission"); } @@ -6427,7 +6427,7 @@ public class AudioService extends IAudioService.Stub return false; } boolean suppress = false; - if (resolvedStream == DEFAULT_VOL_STREAM_NO_PLAYBACK && mController != null) { + if (resolvedStream != AudioSystem.STREAM_MUSIC && mController != null) { final long now = SystemClock.uptimeMillis(); if ((flags & AudioManager.FLAG_SHOW_UI) != 0 && !mVisible) { // ui will become visible diff --git a/services/core/java/com/android/server/backup/PeopleBackupHelper.java b/services/core/java/com/android/server/backup/PeopleBackupHelper.java new file mode 100644 index 000000000000..e58a051544d8 --- /dev/null +++ b/services/core/java/com/android/server/backup/PeopleBackupHelper.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.backup; + +import android.app.backup.BlobBackupHelper; +import android.util.Slog; + +import com.android.server.LocalServices; +import com.android.server.people.PeopleServiceInternal; + +class PeopleBackupHelper extends BlobBackupHelper { + + private static final String TAG = PeopleBackupHelper.class.getSimpleName(); + private static final boolean DEBUG = false; + + // Current schema of the backup state blob. + private static final int STATE_VERSION = 1; + + // Key under which conversation infos state blob is committed to backup. + private static final String KEY_CONVERSATIONS = "people_conversation_infos"; + + private final int mUserId; + + PeopleBackupHelper(int userId) { + super(STATE_VERSION, KEY_CONVERSATIONS); + mUserId = userId; + } + + @Override + protected byte[] getBackupPayload(String key) { + if (!KEY_CONVERSATIONS.equals(key)) { + Slog.w(TAG, "Unexpected backup key " + key); + return new byte[0]; + } + PeopleServiceInternal ps = LocalServices.getService(PeopleServiceInternal.class); + if (DEBUG) { + Slog.d(TAG, "Handling backup of " + key); + } + return ps.backupConversationInfos(mUserId); + } + + @Override + protected void applyRestoredPayload(String key, byte[] payload) { + if (!KEY_CONVERSATIONS.equals(key)) { + Slog.w(TAG, "Unexpected restore key " + key); + return; + } + PeopleServiceInternal ps = LocalServices.getService(PeopleServiceInternal.class); + if (DEBUG) { + Slog.d(TAG, "Handling restore of " + key); + } + ps.restoreConversationInfos(mUserId, key, payload); + } +} diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java index 35e8f56cf36d..1f4563b801fe 100644 --- a/services/core/java/com/android/server/backup/SystemBackupAgent.java +++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java @@ -55,6 +55,7 @@ public class SystemBackupAgent extends BackupAgentHelper { private static final String SHORTCUT_MANAGER_HELPER = "shortcut_manager"; private static final String ACCOUNT_MANAGER_HELPER = "account_manager"; private static final String SLICES_HELPER = "slices"; + private static final String PEOPLE_HELPER = "people"; // These paths must match what the WallpaperManagerService uses. The leaf *_FILENAME // are also used in the full-backup file format, so must not change unless steps are @@ -99,6 +100,7 @@ public class SystemBackupAgent extends BackupAgentHelper { addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper()); addHelper(ACCOUNT_MANAGER_HELPER, new AccountManagerBackupHelper()); addHelper(SLICES_HELPER, new SliceBackupHelper(this)); + addHelper(PEOPLE_HELPER, new PeopleBackupHelper(mUserId)); } @Override diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index e1a9f3b97e9a..8dd3242c947a 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -1400,7 +1400,8 @@ public class BiometricService extends SystemService { if (mCurrentAuthSession.mTokenEscrow != null) { mKeyStore.addAuthToken(mCurrentAuthSession.mTokenEscrow); } - mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded(); + mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded( + Utils.getAuthenticationTypeForResult(reason)); break; case BiometricPrompt.DISMISSED_REASON_NEGATIVE: diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java index 19f535876274..389763b5377a 100644 --- a/services/core/java/com/android/server/biometrics/Utils.java +++ b/services/core/java/com/android/server/biometrics/Utils.java @@ -22,6 +22,7 @@ import android.content.Context; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricPrompt; +import android.hardware.biometrics.BiometricPrompt.AuthenticationResultType; import android.os.Build; import android.os.Bundle; import android.os.UserHandle; @@ -210,4 +211,30 @@ public class Utils { } return biometricManagerCode; } + + /** + * Converts a {@link BiometricPrompt} dismissal reason to an authentication type at the level of + * granularity supported by {@link BiometricPrompt.AuthenticationResult}. + * + * @param reason The reason that the {@link BiometricPrompt} was dismissed. Must be one of: + * {@link BiometricPrompt#DISMISSED_REASON_CREDENTIAL_CONFIRMED}, + * {@link BiometricPrompt#DISMISSED_REASON_BIOMETRIC_CONFIRMED}, or + * {@link BiometricPrompt#DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED} + * @return An integer representing the authentication type for {@link + * BiometricPrompt.AuthenticationResult}. + * @throws IllegalArgumentException if given an invalid dismissal reason. + */ + public static @AuthenticationResultType int getAuthenticationTypeForResult(int reason) { + switch (reason) { + case BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED: + return BiometricPrompt.AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL; + + case BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED: + case BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED: + return BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC; + + default: + throw new IllegalArgumentException("Unsupported dismissal reason: " + reason); + } + } } diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java index f15d999e1006..8d261762a19b 100644 --- a/services/core/java/com/android/server/compat/CompatConfig.java +++ b/services/core/java/com/android/server/compat/CompatConfig.java @@ -367,6 +367,8 @@ final class CompatConfig { CompatConfig config = new CompatConfig(androidBuildClassifier, context); config.initConfigFromLib(Environment.buildPath( Environment.getRootDirectory(), "etc", "compatconfig")); + config.initConfigFromLib(Environment.buildPath( + Environment.getRootDirectory(), "system_ext", "etc", "compatconfig")); return config; } diff --git a/services/core/java/com/android/server/connectivity/DataConnectionStats.java b/services/core/java/com/android/server/connectivity/DataConnectionStats.java index 1b1c54682255..5010e46a74eb 100644 --- a/services/core/java/com/android/server/connectivity/DataConnectionStats.java +++ b/services/core/java/com/android/server/connectivity/DataConnectionStats.java @@ -16,6 +16,9 @@ package com.android.server.connectivity; +import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN; +import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS; + import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -24,6 +27,7 @@ import android.net.ConnectivityManager; import android.os.Handler; import android.os.Looper; import android.os.RemoteException; +import android.telephony.NetworkRegistrationInfo; import android.telephony.PhoneStateListener; import android.telephony.ServiceState; import android.telephony.SignalStrength; @@ -91,7 +95,10 @@ public class DataConnectionStats extends BroadcastReceiver { boolean visible = (simReadyOrUnknown || isCdma()) // we only check the sim state for GSM && hasService() && mDataState == TelephonyManager.DATA_CONNECTED; - int networkType = mServiceState.getDataNetworkType(); + NetworkRegistrationInfo regInfo = + mServiceState.getNetworkRegistrationInfo(DOMAIN_PS, TRANSPORT_TYPE_WWAN); + int networkType = regInfo == null ? TelephonyManager.NETWORK_TYPE_UNKNOWN + : regInfo.getAccessNetworkTechnology(); if (DEBUG) Log.d(TAG, String.format("Noting data connection for network type %s: %svisible", networkType, visible ? "" : "not ")); try { diff --git a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java index 6fa999cb039a..04c792ae482b 100644 --- a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java +++ b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java @@ -26,6 +26,7 @@ import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkPolicy.LIMIT_DISABLED; import static android.net.NetworkPolicy.WARNING_DISABLED; import static android.provider.Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES; +import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH; import static com.android.server.net.NetworkPolicyManagerService.OPPORTUNISTIC_QUOTA_UNKNOWN; @@ -46,19 +47,18 @@ import android.net.NetworkIdentity; import android.net.NetworkPolicy; import android.net.NetworkPolicyManager; import android.net.NetworkRequest; +import android.net.NetworkSpecifier; import android.net.NetworkStats; import android.net.NetworkTemplate; -import android.net.StringNetworkSpecifier; +import android.net.TelephonyNetworkSpecifier; +import android.net.Uri; import android.os.BestClock; import android.os.Handler; import android.os.SystemClock; -import android.net.Uri; import android.os.UserHandle; import android.provider.Settings; import android.telephony.TelephonyManager; -import android.util.DataUnit; import android.util.DebugUtils; -import android.util.Pair; import android.util.Range; import android.util.Slog; @@ -74,7 +74,6 @@ import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; -import java.util.Iterator; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; @@ -185,7 +184,6 @@ public class MultipathPolicyTracker { // Track information on mobile networks as they come and go. class MultipathTracker { final Network network; - final int subId; final String subscriberId; private long mQuota; @@ -198,13 +196,14 @@ public class MultipathPolicyTracker { public MultipathTracker(Network network, NetworkCapabilities nc) { this.network = network; this.mNetworkCapabilities = new NetworkCapabilities(nc); - try { - subId = Integer.parseInt( - ((StringNetworkSpecifier) nc.getNetworkSpecifier()).toString()); - } catch (ClassCastException | NullPointerException | NumberFormatException e) { + NetworkSpecifier specifier = nc.getNetworkSpecifier(); + int subId = INVALID_SUBSCRIPTION_ID; + if (specifier instanceof TelephonyNetworkSpecifier) { + subId = ((TelephonyNetworkSpecifier) specifier).getSubscriptionId(); + } else { throw new IllegalStateException(String.format( - "Can't get subId from mobile network %s (%s): %s", - network, nc, e.getMessage())); + "Can't get subId from mobile network %s (%s)", + network, nc)); } TelephonyManager tele = mContext.getSystemService(TelephonyManager.class); diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java index 21795184b1bd..2c415570d5fa 100644 --- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java +++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java @@ -28,7 +28,7 @@ import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.net.NetworkSpecifier; -import android.net.StringNetworkSpecifier; +import android.net.TelephonyNetworkSpecifier; import android.net.wifi.WifiInfo; import android.os.UserHandle; import android.telephony.SubscriptionManager; @@ -223,14 +223,8 @@ public class NetworkNotificationManager { // name has been added to it NetworkSpecifier specifier = nai.networkCapabilities.getNetworkSpecifier(); int subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID; - if (specifier instanceof StringNetworkSpecifier) { - try { - subId = Integer.parseInt( - ((StringNetworkSpecifier) specifier).specifier); - } catch (NumberFormatException e) { - Slog.e(TAG, "NumberFormatException on " - + ((StringNetworkSpecifier) specifier).specifier); - } + if (specifier instanceof TelephonyNetworkSpecifier) { + subId = ((TelephonyNetworkSpecifier) specifier).getSubscriptionId(); } details = mTelephonyManager.createForSubscriptionId(subId) diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 476f3f823def..1d2a9059c453 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -848,7 +848,7 @@ public class Vpn { } public int getNetId() { - return mNetworkAgent != null ? mNetworkAgent.netId : NETID_UNSET; + return mNetworkAgent != null ? mNetworkAgent.network.netId : NETID_UNSET; } private LinkProperties makeLinkProperties() { diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java index 8498dcb32eb1..decb1f9bbcf7 100644 --- a/services/core/java/com/android/server/display/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/DisplayModeDirector.java @@ -73,7 +73,7 @@ public class DisplayModeDirector { private static final int GLOBAL_ID = -1; // The tolerance within which we consider something approximately equals. - private static final float EPSILON = 0.001f; + private static final float EPSILON = 0.01f; private final Object mLock = new Object(); private final Context mContext; diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java index 2c3c70fc3da2..9c421524d723 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java @@ -68,8 +68,9 @@ public abstract class InputMethodManagerInternal { * @param autofillId {@link AutofillId} of currently focused field. * @param cb {@link IInlineSuggestionsRequestCallback} used to pass back the request object. */ - public abstract void onCreateInlineSuggestionsRequest(ComponentName componentName, - AutofillId autofillId, IInlineSuggestionsRequestCallback cb); + public abstract void onCreateInlineSuggestionsRequest(@UserIdInt int userId, + ComponentName componentName, AutofillId autofillId, + IInlineSuggestionsRequestCallback cb); /** * Force switch to the enabled input method by {@code imeId} for current user. If the input @@ -107,8 +108,9 @@ public abstract class InputMethodManagerInternal { } @Override - public void onCreateInlineSuggestionsRequest(ComponentName componentName, - AutofillId autofillId, IInlineSuggestionsRequestCallback cb) { + public void onCreateInlineSuggestionsRequest(int userId, + ComponentName componentName, AutofillId autofillId, + IInlineSuggestionsRequestCallback cb) { } @Override diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index d4b13faf3195..0bf65bd6f739 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -111,6 +111,7 @@ import android.view.WindowManager.LayoutParams; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import android.view.autofill.AutofillId; import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InlineSuggestionsRequest; import android.view.inputmethod.InputBinding; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputConnectionInspector; @@ -142,6 +143,7 @@ import com.android.internal.os.TransferPipe; import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.view.IInlineSuggestionsRequestCallback; +import com.android.internal.view.IInlineSuggestionsResponseCallback; import com.android.internal.view.IInputContext; import com.android.internal.view.IInputMethod; import com.android.internal.view.IInputMethodClient; @@ -1790,15 +1792,18 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } @GuardedBy("mMethodMap") - private void onCreateInlineSuggestionsRequestLocked(ComponentName componentName, - AutofillId autofillId, IInlineSuggestionsRequestCallback callback) { - + private void onCreateInlineSuggestionsRequestLocked(@UserIdInt int userId, + ComponentName componentName, AutofillId autofillId, + IInlineSuggestionsRequestCallback callback) { final InputMethodInfo imi = mMethodMap.get(mCurMethodId); try { - if (imi != null && imi.isInlineSuggestionsEnabled() && mCurMethod != null) { + if (userId == mSettings.getCurrentUserId() && imi != null + && imi.isInlineSuggestionsEnabled() && mCurMethod != null) { executeOrSendMessage(mCurMethod, mCaller.obtainMessageOOOO(MSG_INLINE_SUGGESTIONS_REQUEST, mCurMethod, - componentName, autofillId, callback)); + componentName, autofillId, + new InlineSuggestionsRequestCallbackDecorator(callback, + imi.getPackageName()))); } else { callback.onInlineSuggestionsUnsupported(); } @@ -1808,6 +1813,42 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } /** + * The decorator which validates the host package name in the + * {@link InlineSuggestionsRequest} argument to make sure it matches the IME package name. + */ + private static final class InlineSuggestionsRequestCallbackDecorator + extends IInlineSuggestionsRequestCallback.Stub { + @NonNull + private final IInlineSuggestionsRequestCallback mCallback; + @NonNull + private final String mImePackageName; + + InlineSuggestionsRequestCallbackDecorator( + @NonNull IInlineSuggestionsRequestCallback callback, + @NonNull String imePackageName) { + mCallback = callback; + mImePackageName = imePackageName; + } + + @Override + public void onInlineSuggestionsUnsupported() throws RemoteException { + mCallback.onInlineSuggestionsUnsupported(); + } + + @Override + public void onInlineSuggestionsRequest(InlineSuggestionsRequest request, + IInlineSuggestionsResponseCallback callback) throws RemoteException { + if (!mImePackageName.equals(request.getHostPackageName())) { + throw new SecurityException( + "Host package name in the provide request=[" + request.getHostPackageName() + + "] doesn't match the IME package name=[" + mImePackageName + + "]."); + } + mCallback.onInlineSuggestionsRequest(request, callback); + } + } + + /** * @param imiId if null, returns enabled subtypes for the current imi * @return enabled subtypes of the specified imi */ @@ -4471,10 +4512,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - private void onCreateInlineSuggestionsRequest(ComponentName componentName, - AutofillId autofillId, IInlineSuggestionsRequestCallback callback) { + private void onCreateInlineSuggestionsRequest(@UserIdInt int userId, + ComponentName componentName, AutofillId autofillId, + IInlineSuggestionsRequestCallback callback) { synchronized (mMethodMap) { - onCreateInlineSuggestionsRequestLocked(componentName, autofillId, callback); + onCreateInlineSuggestionsRequestLocked(userId, componentName, autofillId, callback); } } @@ -4542,9 +4584,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } @Override - public void onCreateInlineSuggestionsRequest(ComponentName componentName, + public void onCreateInlineSuggestionsRequest(int userId, ComponentName componentName, AutofillId autofillId, IInlineSuggestionsRequestCallback cb) { - mService.onCreateInlineSuggestionsRequest(componentName, autofillId, cb); + mService.onCreateInlineSuggestionsRequest(userId, componentName, autofillId, cb); } @Override diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java index 20d7955e0c77..f09795fbea01 100644 --- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java @@ -191,8 +191,9 @@ public final class MultiClientInputMethodManagerService { } @Override - public void onCreateInlineSuggestionsRequest(ComponentName componentName, - AutofillId autofillId, IInlineSuggestionsRequestCallback cb) { + public void onCreateInlineSuggestionsRequest(int userId, + ComponentName componentName, AutofillId autofillId, + IInlineSuggestionsRequestCallback cb) { try { //TODO(b/137800469): support multi client IMEs. cb.onInlineSuggestionsUnsupported(); diff --git a/services/core/java/com/android/server/integrity/IntegrityFileManager.java b/services/core/java/com/android/server/integrity/IntegrityFileManager.java index 17a4b9c6c170..fffe7d9030ff 100644 --- a/services/core/java/com/android/server/integrity/IntegrityFileManager.java +++ b/services/core/java/com/android/server/integrity/IntegrityFileManager.java @@ -24,6 +24,7 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.server.integrity.model.RuleMetadata; +import com.android.server.integrity.parser.RandomAccessObject; import com.android.server.integrity.parser.RuleBinaryParser; import com.android.server.integrity.parser.RuleIndexRange; import com.android.server.integrity.parser.RuleIndexingController; @@ -64,10 +65,8 @@ public class IntegrityFileManager { // update rules atomically. private final File mStagingDir; - @Nullable - private RuleMetadata mRuleMetadataCache; - @Nullable - private RuleIndexingController mRuleIndexingController; + @Nullable private RuleMetadata mRuleMetadataCache; + @Nullable private RuleIndexingController mRuleIndexingController; /** Get the singleton instance of this class. */ public static synchronized IntegrityFileManager getInstance() { @@ -132,9 +131,9 @@ public class IntegrityFileManager { } try (FileOutputStream ruleFileOutputStream = - new FileOutputStream(new File(mStagingDir, RULES_FILE)); - FileOutputStream indexingFileOutputStream = - new FileOutputStream(new File(mStagingDir, INDEXING_FILE))) { + new FileOutputStream(new File(mStagingDir, RULES_FILE)); + FileOutputStream indexingFileOutputStream = + new FileOutputStream(new File(mStagingDir, INDEXING_FILE))) { mRuleSerializer.serialize( rules, Optional.empty(), ruleFileOutputStream, indexingFileOutputStream); } @@ -164,11 +163,10 @@ public class IntegrityFileManager { } // Read the rules based on the index information when available. - try (FileInputStream inputStream = - new FileInputStream(new File(mRulesDir, RULES_FILE))) { - List<Rule> rules = mRuleParser.parse(inputStream, ruleReadingIndexes); - return rules; - } + File ruleFile = new File(mRulesDir, RULES_FILE); + List<Rule> rules = + mRuleParser.parse(RandomAccessObject.ofFile(ruleFile), ruleReadingIndexes); + return rules; } } @@ -187,6 +185,10 @@ public class IntegrityFileManager { && tmpDir.renameTo(mStagingDir))) { throw new IOException("Error switching staging/rules directory"); } + + for (File file : mStagingDir.listFiles()) { + file.delete(); + } } } diff --git a/services/core/java/com/android/server/integrity/model/BitInputStream.java b/services/core/java/com/android/server/integrity/model/BitInputStream.java index e768fe6626ac..e7cc81eab26b 100644 --- a/services/core/java/com/android/server/integrity/model/BitInputStream.java +++ b/services/core/java/com/android/server/integrity/model/BitInputStream.java @@ -19,26 +19,21 @@ package com.android.server.integrity.model; import java.io.IOException; import java.io.InputStream; -/** A wrapper class for reading a stream of bits. */ +/** A wrapper class for reading a stream of bits. + * + * <p>Note: this class reads from underlying stream byte-by-byte. It is advised to apply buffering + * to underlying streams. + */ public class BitInputStream { - private long mBitPointer; - private boolean mReadFromStream; + private long mBitsRead; - private byte[] mRuleBytes; - private InputStream mRuleInputStream; + private InputStream mInputStream; - private byte mCurrentRuleByte; - - public BitInputStream(byte[] ruleBytes) { - this.mRuleBytes = ruleBytes; - this.mBitPointer = 0; - this.mReadFromStream = false; - } + private byte mCurrentByte; - public BitInputStream(InputStream ruleInputStream) { - this.mRuleInputStream = ruleInputStream; - this.mReadFromStream = true; + public BitInputStream(InputStream inputStream) { + mInputStream = inputStream; } /** @@ -52,15 +47,15 @@ public class BitInputStream { int count = 0; while (count++ < numOfBits) { - if (mBitPointer % 8 == 0) { - mCurrentRuleByte = getNextByte(); + if (mBitsRead % 8 == 0) { + mCurrentByte = getNextByte(); } - int offset = 7 - (int) (mBitPointer % 8); + int offset = 7 - (int) (mBitsRead % 8); component <<= 1; - component |= (mCurrentRuleByte >>> offset) & 1; + component |= (mCurrentByte >>> offset) & 1; - mBitPointer++; + mBitsRead++; } return component; @@ -68,22 +63,10 @@ public class BitInputStream { /** Check if there are bits left in the stream. */ public boolean hasNext() throws IOException { - if (mReadFromStream) { - return mRuleInputStream.available() > 0; - } else { - return mBitPointer / 8 < mRuleBytes.length; - } + return mInputStream.available() > 0; } private byte getNextByte() throws IOException { - if (mReadFromStream) { - return (byte) mRuleInputStream.read(); - } else { - int idx = (int) (mBitPointer / 8); - if (idx >= mRuleBytes.length) { - throw new IllegalArgumentException(String.format("Invalid byte index: %d", idx)); - } - return mRuleBytes[idx]; - } + return (byte) mInputStream.read(); } } diff --git a/services/core/java/com/android/server/integrity/model/BitOutputStream.java b/services/core/java/com/android/server/integrity/model/BitOutputStream.java index b8ea041c4196..7d1bb3fb8203 100644 --- a/services/core/java/com/android/server/integrity/model/BitOutputStream.java +++ b/services/core/java/com/android/server/integrity/model/BitOutputStream.java @@ -16,17 +16,26 @@ package com.android.server.integrity.model; -import java.util.BitSet; +import static com.android.server.integrity.model.ComponentBitSize.BYTE_BITS; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; /** A wrapper class for writing a stream of bits. */ public class BitOutputStream { - private BitSet mBitSet; - private int mIndex; + private static final int BUFFER_SIZE = 4 * 1024; + + private int mNextBitIndex; + + private final OutputStream mOutputStream; + private final byte[] mBuffer; - public BitOutputStream() { - mBitSet = new BitSet(); - mIndex = 0; + public BitOutputStream(OutputStream outputStream) { + mBuffer = new byte[BUFFER_SIZE]; + mNextBitIndex = 0; + mOutputStream = outputStream; } /** @@ -35,15 +44,17 @@ public class BitOutputStream { * @param numOfBits The number of bits used to represent the value. * @param value The value to convert to bits. */ - public void setNext(int numOfBits, int value) { + public void setNext(int numOfBits, int value) throws IOException { if (numOfBits <= 0) { return; } - int offset = 1 << (numOfBits - 1); + + // optional: we can do some clever size checking to "OR" an entire segment of bits instead + // of setting bits one by one, but it is probably not worth it. + int nextBitMask = 1 << (numOfBits - 1); while (numOfBits-- > 0) { - mBitSet.set(mIndex, (value & offset) != 0); - offset >>>= 1; - mIndex++; + setNext((value & nextBitMask) != 0); + nextBitMask >>>= 1; } } @@ -52,35 +63,43 @@ public class BitOutputStream { * * @param value The value to set the bit to. */ - public void setNext(boolean value) { - mBitSet.set(mIndex, value); - mIndex++; + public void setNext(boolean value) throws IOException { + int byteToWrite = mNextBitIndex / BYTE_BITS; + if (byteToWrite == BUFFER_SIZE) { + mOutputStream.write(mBuffer); + reset(); + byteToWrite = 0; + } + if (value) { + mBuffer[byteToWrite] |= 1 << (BYTE_BITS - 1 - (mNextBitIndex % BYTE_BITS)); + } + mNextBitIndex++; } /** Set the next bit in the stream to true. */ - public void setNext() { + public void setNext() throws IOException { setNext(/* value= */ true); } - /** Convert BitSet in big-endian to ByteArray in big-endian. */ - public byte[] toByteArray() { - int bitSetSize = mBitSet.length(); - int numOfBytes = bitSetSize / 8; - if (bitSetSize % 8 != 0) { - numOfBytes++; - } - byte[] bytes = new byte[numOfBytes]; - for (int i = 0; i < mBitSet.length(); i++) { - if (mBitSet.get(i)) { - bytes[i / 8] |= 1 << (7 - (i % 8)); - } + /** + * Flush the data written to the underlying {@link java.io.OutputStream}. Any unfinished bytes + * will be padded with 0. + */ + public void flush() throws IOException { + int endByte = mNextBitIndex / BYTE_BITS; + if (mNextBitIndex % BYTE_BITS != 0) { + // If next bit is not the first bit of a byte, then mNextBitIndex / BYTE_BITS would be + // the byte that includes already written bits. We need to increment it so this byte + // gets written. + endByte++; } - return bytes; + mOutputStream.write(mBuffer, 0, endByte); + reset(); } - /** Clear the stream. */ - public void clear() { - mBitSet.clear(); - mIndex = 0; + /** Reset this output stream to start state. */ + private void reset() { + mNextBitIndex = 0; + Arrays.fill(mBuffer, (byte) 0); } } diff --git a/services/core/java/com/android/server/integrity/model/BitTrackedInputStream.java b/services/core/java/com/android/server/integrity/model/BitTrackedInputStream.java deleted file mode 100644 index e555e3e5746e..000000000000 --- a/services/core/java/com/android/server/integrity/model/BitTrackedInputStream.java +++ /dev/null @@ -1,69 +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 com.android.server.integrity.model; - -import java.io.IOException; -import java.io.InputStream; - -/** - * An input stream that tracks the total number read bytes since construction and allows moving - * fast forward to a certain byte any time during the execution. - * - * This class is used for efficient reading of rules based on the rule indexing. - */ -public class BitTrackedInputStream extends BitInputStream { - - private static int sReadBitsCount; - - /** Constructor with byte array. */ - public BitTrackedInputStream(byte[] inputStream) { - super(inputStream); - sReadBitsCount = 0; - } - - /** Constructor with input stream. */ - public BitTrackedInputStream(InputStream inputStream) { - super(inputStream); - sReadBitsCount = 0; - } - - /** Obtains an integer value of the next {@code numOfBits}. */ - @Override - public int getNext(int numOfBits) throws IOException { - sReadBitsCount += numOfBits; - return super.getNext(numOfBits); - } - - /** Returns the current cursor position showing the number of bits that are read. */ - public int getReadBitsCount() { - return sReadBitsCount; - } - - /** - * Sets the cursor to the specified byte location. - * - * Note that the integer parameter specifies the location in bytes -- not bits. - */ - public void setCursorToByteLocation(int byteLocation) throws IOException { - int bitCountToRead = byteLocation * 8 - sReadBitsCount; - if (bitCountToRead < 0) { - throw new IllegalStateException("The byte position is already read."); - } - super.getNext(bitCountToRead); - sReadBitsCount = byteLocation * 8; - } -} diff --git a/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java b/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java index f575599e1c49..ceed054e4a53 100644 --- a/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java +++ b/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java @@ -23,31 +23,41 @@ import java.io.OutputStream; * An output stream that tracks the total number written bytes since construction and allows * querying this value any time during the execution. * - * This class is used for constructing the rule indexing. + * <p>This class is used for constructing the rule indexing. */ -public class ByteTrackedOutputStream { +public class ByteTrackedOutputStream extends OutputStream { - private static int sWrittenBytesCount; - private static OutputStream sOutputStream; + private int mWrittenBytesCount; + private final OutputStream mOutputStream; public ByteTrackedOutputStream(OutputStream outputStream) { - sWrittenBytesCount = 0; - sOutputStream = outputStream; + mWrittenBytesCount = 0; + mOutputStream = outputStream; + } + + @Override + public void write(int b) throws IOException { + mWrittenBytesCount++; + mOutputStream.write(b); } /** - * Writes the given bytes into the output stream provided in constructor and updates the - * total number of written bytes. + * Writes the given bytes into the output stream provided in constructor and updates the total + * number of written bytes. */ + @Override public void write(byte[] bytes) throws IOException { - sWrittenBytesCount += bytes.length; - sOutputStream.write(bytes); + write(bytes, 0, bytes.length); } - /** - * Returns the total number of bytes written into the output stream at the requested time. - */ + @Override + public void write(byte[] b, int off, int len) throws IOException { + mWrittenBytesCount += len; + mOutputStream.write(b, off, len); + } + + /** Returns the total number of bytes written into the output stream at the requested time. */ public int getWrittenBytesCount() { - return sWrittenBytesCount; + return mWrittenBytesCount; } } diff --git a/services/core/java/com/android/server/integrity/model/ComponentBitSize.java b/services/core/java/com/android/server/integrity/model/ComponentBitSize.java index 6ec2d5f70372..c3899638f40f 100644 --- a/services/core/java/com/android/server/integrity/model/ComponentBitSize.java +++ b/services/core/java/com/android/server/integrity/model/ComponentBitSize.java @@ -23,13 +23,14 @@ import android.content.integrity.Rule; * components. */ public final class ComponentBitSize { - public static final int FORMAT_VERSION_BITS = 5; + public static final int FORMAT_VERSION_BITS = 8; + public static final int EFFECT_BITS = 3; public static final int KEY_BITS = 4; public static final int OPERATOR_BITS = 3; public static final int CONNECTOR_BITS = 2; public static final int SEPARATOR_BITS = 2; - public static final int VALUE_SIZE_BITS = 6; + public static final int VALUE_SIZE_BITS = 8; public static final int IS_HASHED_BITS = 1; public static final int ATOMIC_FORMULA_START = 0; @@ -38,4 +39,6 @@ public final class ComponentBitSize { public static final int DEFAULT_FORMAT_VERSION = 1; public static final int SIGNAL_BIT = 1; + + public static final int BYTE_BITS = 8; } diff --git a/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java b/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java index 52df898706d6..0c4052adc3ed 100644 --- a/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java +++ b/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java @@ -18,9 +18,9 @@ package com.android.server.integrity.model; /** A helper class containing special indexing file constants. */ public final class IndexingFileConstants { - // The parsing time seems acceptable for this block size based on the tests in - // go/ic-rule-file-format. - public static final int INDEXING_BLOCK_SIZE = 100; + // We empirically experimented with different block sizes and identified that 50 is in the + // optimal range of efficient computation. + public static final int INDEXING_BLOCK_SIZE = 50; public static final String START_INDEXING_KEY = "START_KEY"; public static final String END_INDEXING_KEY = "END_KEY"; diff --git a/services/core/java/com/android/server/integrity/parser/LimitInputStream.java b/services/core/java/com/android/server/integrity/parser/LimitInputStream.java new file mode 100644 index 000000000000..a91bbb7dbae1 --- /dev/null +++ b/services/core/java/com/android/server/integrity/parser/LimitInputStream.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.integrity.parser; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** An {@link InputStream} that basically truncates another {@link InputStream} */ +public class LimitInputStream extends FilterInputStream { + private int mReadBytes; + private final int mLimit; + + public LimitInputStream(InputStream in, int limit) { + super(in); + if (limit < 0) { + throw new IllegalArgumentException("limit " + limit + " cannot be negative"); + } + mReadBytes = 0; + mLimit = limit; + } + + @Override + public int available() throws IOException { + return Math.min(super.available(), mLimit - mReadBytes); + } + + @Override + public int read() throws IOException { + if (mReadBytes == mLimit) { + return -1; + } + mReadBytes++; + return super.read(); + } + + @Override + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (len <= 0) { + return 0; + } + int available = available(); + if (available <= 0) { + return -1; + } + int result = super.read(b, off, Math.min(len, available)); + mReadBytes += result; + return result; + } + + @Override + public long skip(long n) throws IOException { + if (n <= 0) { + return 0; + } + int available = available(); + if (available <= 0) { + return 0; + } + int bytesToSkip = (int) Math.min(available, n); + long bytesSkipped = super.skip(bytesToSkip); + mReadBytes += (int) bytesSkipped; + return bytesSkipped; + } +} diff --git a/services/core/java/com/android/server/integrity/parser/RandomAccessInputStream.java b/services/core/java/com/android/server/integrity/parser/RandomAccessInputStream.java new file mode 100644 index 000000000000..206e6a1f0197 --- /dev/null +++ b/services/core/java/com/android/server/integrity/parser/RandomAccessInputStream.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.integrity.parser; + +import java.io.IOException; +import java.io.InputStream; + +/** A wrapper around {@link RandomAccessObject} to turn it into a {@link InputStream}. */ +public class RandomAccessInputStream extends InputStream { + + private final RandomAccessObject mRandomAccessObject; + + private int mPosition; + + public RandomAccessInputStream(RandomAccessObject object) throws IOException { + mRandomAccessObject = object; + mPosition = 0; + } + + /** Returns the position of the file pointer. */ + public int getPosition() { + return mPosition; + } + + /** See {@link RandomAccessObject#seek(int)} */ + public void seek(int position) throws IOException { + mRandomAccessObject.seek(position); + mPosition = position; + } + + @Override + public int available() throws IOException { + return mRandomAccessObject.length() - mPosition; + } + + @Override + public void close() throws IOException { + mRandomAccessObject.close(); + } + + @Override + public int read() throws IOException { + if (available() <= 0) { + return -1; + } + mPosition++; + return mRandomAccessObject.read(); + } + + @Override + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (len <= 0) { + return 0; + } + int available = available(); + if (available <= 0) { + return -1; + } + int result = mRandomAccessObject.read(b, off, Math.min(len, available)); + mPosition += result; + return result; + } + + @Override + public long skip(long n) throws IOException { + if (n <= 0) { + return 0; + } + int available = available(); + if (available <= 0) { + return 0; + } + int skipAmount = (int) Math.min(available, n); + mPosition += skipAmount; + mRandomAccessObject.seek(mPosition); + return skipAmount; + } +} diff --git a/services/core/java/com/android/server/integrity/parser/RandomAccessObject.java b/services/core/java/com/android/server/integrity/parser/RandomAccessObject.java new file mode 100644 index 000000000000..d9b2e38b0062 --- /dev/null +++ b/services/core/java/com/android/server/integrity/parser/RandomAccessObject.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.integrity.parser; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; + +/** An interface for random access objects like RandomAccessFile or byte arrays. */ +public abstract class RandomAccessObject { + + /** See {@link RandomAccessFile#seek(long)}. */ + public abstract void seek(int position) throws IOException; + + /** See {@link RandomAccessFile#read()}. */ + public abstract int read() throws IOException; + + /** See {@link RandomAccessFile#read(byte[], int, int)}. */ + public abstract int read(byte[] bytes, int off, int len) throws IOException; + + /** See {@link RandomAccessFile#close()}. */ + public abstract void close() throws IOException; + + /** See {@link java.io.RandomAccessFile#length()}. */ + public abstract int length(); + + /** Static constructor from a file. */ + public static RandomAccessObject ofFile(File file) throws IOException { + return new RandomAccessFileObject(file); + } + + /** Static constructor from a byte array. */ + public static RandomAccessObject ofBytes(byte[] bytes) { + return new RandomAccessByteArrayObject(bytes); + } + + private static class RandomAccessFileObject extends RandomAccessObject { + private final RandomAccessFile mRandomAccessFile; + // We cache the length since File.length() invokes file IO. + private final int mLength; + + RandomAccessFileObject(File file) throws IOException { + long length = file.length(); + if (length > Integer.MAX_VALUE) { + throw new IOException("Unsupported file size (too big) " + length); + } + + mRandomAccessFile = new RandomAccessFile(file, /* mode= */ "r"); + mLength = (int) length; + } + + @Override + public void seek(int position) throws IOException { + mRandomAccessFile.seek(position); + } + + @Override + public int read() throws IOException { + return mRandomAccessFile.read(); + } + + @Override + public int read(byte[] bytes, int off, int len) throws IOException { + return mRandomAccessFile.read(bytes, off, len); + } + + @Override + public void close() throws IOException { + mRandomAccessFile.close(); + } + + @Override + public int length() { + return mLength; + } + } + + private static class RandomAccessByteArrayObject extends RandomAccessObject { + + private final ByteBuffer mBytes; + + RandomAccessByteArrayObject(byte[] bytes) { + mBytes = ByteBuffer.wrap(bytes); + } + + @Override + public void seek(int position) throws IOException { + mBytes.position(position); + } + + @Override + public int read() throws IOException { + if (!mBytes.hasRemaining()) { + return -1; + } + + return mBytes.get() & 0xFF; + } + + @Override + public int read(byte[] bytes, int off, int len) throws IOException { + int bytesToCopy = Math.min(len, mBytes.remaining()); + if (bytesToCopy <= 0) { + return 0; + } + mBytes.get(bytes, off, len); + return bytesToCopy; + } + + @Override + public void close() throws IOException {} + + @Override + public int length() { + return mBytes.capacity(); + } + } +} diff --git a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java index e744326c49db..90954ff21d57 100644 --- a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java +++ b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java @@ -17,6 +17,7 @@ package com.android.server.integrity.parser; import static com.android.server.integrity.model.ComponentBitSize.ATOMIC_FORMULA_START; +import static com.android.server.integrity.model.ComponentBitSize.BYTE_BITS; import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_END; import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_START; import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS; @@ -37,10 +38,10 @@ import android.content.integrity.CompoundFormula; import android.content.integrity.Formula; import android.content.integrity.Rule; -import com.android.server.integrity.model.BitTrackedInputStream; +import com.android.server.integrity.model.BitInputStream; +import java.io.BufferedInputStream; import java.io.IOException; -import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -50,45 +51,42 @@ public class RuleBinaryParser implements RuleParser { @Override public List<Rule> parse(byte[] ruleBytes) throws RuleParseException { - try { - BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(ruleBytes); - return parseRules(bitTrackedInputStream, /* indexRanges= */ Collections.emptyList()); - } catch (Exception e) { - throw new RuleParseException(e.getMessage(), e); - } + return parse(RandomAccessObject.ofBytes(ruleBytes), Collections.emptyList()); } @Override - public List<Rule> parse(InputStream inputStream, List<RuleIndexRange> indexRanges) + public List<Rule> parse(RandomAccessObject randomAccessObject, List<RuleIndexRange> indexRanges) throws RuleParseException { - try { - BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(inputStream); - return parseRules(bitTrackedInputStream, indexRanges); + try (RandomAccessInputStream randomAccessInputStream = + new RandomAccessInputStream(randomAccessObject)) { + return parseRules(randomAccessInputStream, indexRanges); } catch (Exception e) { throw new RuleParseException(e.getMessage(), e); } } private List<Rule> parseRules( - BitTrackedInputStream bitTrackedInputStream, + RandomAccessInputStream randomAccessInputStream, List<RuleIndexRange> indexRanges) throws IOException { // Read the rule binary file format version. - bitTrackedInputStream.getNext(FORMAT_VERSION_BITS); + randomAccessInputStream.skip(FORMAT_VERSION_BITS / BYTE_BITS); return indexRanges.isEmpty() - ? parseAllRules(bitTrackedInputStream) - : parseIndexedRules(bitTrackedInputStream, indexRanges); + ? parseAllRules(randomAccessInputStream) + : parseIndexedRules(randomAccessInputStream, indexRanges); } - private List<Rule> parseAllRules(BitTrackedInputStream bitTrackedInputStream) + private List<Rule> parseAllRules(RandomAccessInputStream randomAccessInputStream) throws IOException { List<Rule> parsedRules = new ArrayList<>(); - while (bitTrackedInputStream.hasNext()) { - if (bitTrackedInputStream.getNext(SIGNAL_BIT) == 1) { - parsedRules.add(parseRule(bitTrackedInputStream)); + BitInputStream inputStream = + new BitInputStream(new BufferedInputStream(randomAccessInputStream)); + while (inputStream.hasNext()) { + if (inputStream.getNext(SIGNAL_BIT) == 1) { + parsedRules.add(parseRule(inputStream)); } } @@ -96,19 +94,25 @@ public class RuleBinaryParser implements RuleParser { } private List<Rule> parseIndexedRules( - BitTrackedInputStream bitTrackedInputStream, List<RuleIndexRange> indexRanges) + RandomAccessInputStream randomAccessInputStream, + List<RuleIndexRange> indexRanges) throws IOException { List<Rule> parsedRules = new ArrayList<>(); for (RuleIndexRange range : indexRanges) { - // Skip the rules that are not in the range. - bitTrackedInputStream.setCursorToByteLocation(range.getStartIndex()); - - // Read the rules until we reach the end index. - while (bitTrackedInputStream.hasNext() - && bitTrackedInputStream.getReadBitsCount() < range.getEndIndex()) { - if (bitTrackedInputStream.getNext(SIGNAL_BIT) == 1) { - parsedRules.add(parseRule(bitTrackedInputStream)); + randomAccessInputStream.seek(range.getStartIndex()); + + BitInputStream inputStream = + new BitInputStream( + new BufferedInputStream( + new LimitInputStream( + randomAccessInputStream, + range.getEndIndex() - range.getStartIndex()))); + + // Read the rules until we reach the end index. available() here is not reliable. + while (inputStream.hasNext()) { + if (inputStream.getNext(SIGNAL_BIT) == 1) { + parsedRules.add(parseRule(inputStream)); } } } @@ -116,24 +120,24 @@ public class RuleBinaryParser implements RuleParser { return parsedRules; } - private Rule parseRule(BitTrackedInputStream bitTrackedInputStream) throws IOException { - Formula formula = parseFormula(bitTrackedInputStream); - int effect = bitTrackedInputStream.getNext(EFFECT_BITS); + private Rule parseRule(BitInputStream bitInputStream) throws IOException { + Formula formula = parseFormula(bitInputStream); + int effect = bitInputStream.getNext(EFFECT_BITS); - if (bitTrackedInputStream.getNext(SIGNAL_BIT) != 1) { + if (bitInputStream.getNext(SIGNAL_BIT) != 1) { throw new IllegalArgumentException("A rule must end with a '1' bit."); } return new Rule(formula, effect); } - private Formula parseFormula(BitTrackedInputStream bitTrackedInputStream) throws IOException { - int separator = bitTrackedInputStream.getNext(SEPARATOR_BITS); + private Formula parseFormula(BitInputStream bitInputStream) throws IOException { + int separator = bitInputStream.getNext(SEPARATOR_BITS); switch (separator) { case ATOMIC_FORMULA_START: - return parseAtomicFormula(bitTrackedInputStream); + return parseAtomicFormula(bitInputStream); case COMPOUND_FORMULA_START: - return parseCompoundFormula(bitTrackedInputStream); + return parseCompoundFormula(bitInputStream); case COMPOUND_FORMULA_END: return null; default: @@ -142,40 +146,37 @@ public class RuleBinaryParser implements RuleParser { } } - private CompoundFormula parseCompoundFormula(BitTrackedInputStream bitTrackedInputStream) - throws IOException { - int connector = bitTrackedInputStream.getNext(CONNECTOR_BITS); + private CompoundFormula parseCompoundFormula(BitInputStream bitInputStream) throws IOException { + int connector = bitInputStream.getNext(CONNECTOR_BITS); List<Formula> formulas = new ArrayList<>(); - Formula parsedFormula = parseFormula(bitTrackedInputStream); + Formula parsedFormula = parseFormula(bitInputStream); while (parsedFormula != null) { formulas.add(parsedFormula); - parsedFormula = parseFormula(bitTrackedInputStream); + parsedFormula = parseFormula(bitInputStream); } return new CompoundFormula(connector, formulas); } - private AtomicFormula parseAtomicFormula(BitTrackedInputStream bitTrackedInputStream) - throws IOException { - int key = bitTrackedInputStream.getNext(KEY_BITS); - int operator = bitTrackedInputStream.getNext(OPERATOR_BITS); + private AtomicFormula parseAtomicFormula(BitInputStream bitInputStream) throws IOException { + int key = bitInputStream.getNext(KEY_BITS); + int operator = bitInputStream.getNext(OPERATOR_BITS); switch (key) { case AtomicFormula.PACKAGE_NAME: case AtomicFormula.APP_CERTIFICATE: case AtomicFormula.INSTALLER_NAME: case AtomicFormula.INSTALLER_CERTIFICATE: - boolean isHashedValue = bitTrackedInputStream.getNext(IS_HASHED_BITS) == 1; - int valueSize = bitTrackedInputStream.getNext(VALUE_SIZE_BITS); - String stringValue = getStringValue(bitTrackedInputStream, valueSize, - isHashedValue); + boolean isHashedValue = bitInputStream.getNext(IS_HASHED_BITS) == 1; + int valueSize = bitInputStream.getNext(VALUE_SIZE_BITS); + String stringValue = getStringValue(bitInputStream, valueSize, isHashedValue); return new AtomicFormula.StringAtomicFormula(key, stringValue, isHashedValue); case AtomicFormula.VERSION_CODE: - int intValue = getIntValue(bitTrackedInputStream); + int intValue = getIntValue(bitInputStream); return new AtomicFormula.IntAtomicFormula(key, operator, intValue); case AtomicFormula.PRE_INSTALLED: - boolean booleanValue = getBooleanValue(bitTrackedInputStream); + boolean booleanValue = getBooleanValue(bitInputStream); return new AtomicFormula.BooleanAtomicFormula(key, booleanValue); default: throw new IllegalArgumentException(String.format("Unknown key: %d", key)); diff --git a/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java b/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java index 8c8450e5fdc4..595a035baf1d 100644 --- a/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java +++ b/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java @@ -23,28 +23,33 @@ import android.annotation.Nullable; * RuleIndexingController}. */ public class RuleIndexRange { - private static int sStartIndex; - private static int sEndIndex; + private int mStartIndex; + private int mEndIndex; /** Constructor with start and end indexes. */ public RuleIndexRange(int startIndex, int endIndex) { - this.sStartIndex = startIndex; - this.sEndIndex = endIndex; + this.mStartIndex = startIndex; + this.mEndIndex = endIndex; } /** Returns the startIndex. */ public int getStartIndex() { - return sStartIndex; + return mStartIndex; } /** Returns the end index. */ public int getEndIndex() { - return sEndIndex; + return mEndIndex; } @Override public boolean equals(@Nullable Object object) { - return sStartIndex == ((RuleIndexRange) object).getStartIndex() - && sEndIndex == ((RuleIndexRange) object).getEndIndex(); + return mStartIndex == ((RuleIndexRange) object).getStartIndex() + && mEndIndex == ((RuleIndexRange) object).getEndIndex(); + } + + @Override + public String toString() { + return String.format("Range{%d, %d}", mStartIndex, mEndIndex); } } diff --git a/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java index c9713220d6e8..87eee4ea7be4 100644 --- a/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java +++ b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java @@ -28,9 +28,9 @@ import com.android.server.integrity.model.BitInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; -import java.util.TreeSet; import java.util.stream.Collectors; /** Helper class to identify the necessary indexes that needs to be read. */ @@ -56,7 +56,7 @@ public class RuleIndexingController { * read and evaluated. */ public List<RuleIndexRange> identifyRulesToEvaluate(AppInstallMetadata appInstallMetadata) { - ArrayList<RuleIndexRange> indexRanges = new ArrayList(); + List<RuleIndexRange> indexRanges = new ArrayList<>(); // Add the range for package name indexes rules. indexRanges.add( @@ -93,19 +93,28 @@ public class RuleIndexingController { return keyToIndexMap; } - private RuleIndexRange searchIndexingKeysRangeContainingKey( + private static RuleIndexRange searchIndexingKeysRangeContainingKey( LinkedHashMap<String, Integer> indexMap, String searchedKey) { - TreeSet<String> keyTreeSet = - indexMap.keySet().stream() - .filter(key -> !key.matches(START_INDEXING_KEY) && !key.matches( - END_INDEXING_KEY)) - .collect(Collectors.toCollection(TreeSet::new)); + List<String> keys = indexMap.keySet().stream().collect(Collectors.toList()); + List<String> identifiedKeyRange = + searchKeysRangeContainingKey(keys, searchedKey, 0, keys.size() - 1); + return new RuleIndexRange( + indexMap.get(identifiedKeyRange.get(0)), indexMap.get(identifiedKeyRange.get(1))); + } + + private static List<String> searchKeysRangeContainingKey( + List<String> sortedKeyList, String key, int startIndex, int endIndex) { + if (endIndex - startIndex == 1) { + return Arrays.asList(sortedKeyList.get(startIndex), sortedKeyList.get(endIndex)); + } - String minIndex = keyTreeSet.floor(searchedKey); - String maxIndex = keyTreeSet.ceiling(searchedKey); + int midKeyIndex = startIndex + ((endIndex - startIndex) / 2); + String midKey = sortedKeyList.get(midKeyIndex); - return new RuleIndexRange( - indexMap.get(minIndex == null ? START_INDEXING_KEY : minIndex), - indexMap.get(maxIndex == null ? END_INDEXING_KEY : maxIndex)); + if (key.compareTo(midKey) >= 0) { + return searchKeysRangeContainingKey(sortedKeyList, key, midKeyIndex, endIndex); + } else { + return searchKeysRangeContainingKey(sortedKeyList, key, startIndex, midKeyIndex); + } } } diff --git a/services/core/java/com/android/server/integrity/parser/RuleParser.java b/services/core/java/com/android/server/integrity/parser/RuleParser.java index a8e9f6134759..126dacc4fa40 100644 --- a/services/core/java/com/android/server/integrity/parser/RuleParser.java +++ b/services/core/java/com/android/server/integrity/parser/RuleParser.java @@ -18,7 +18,6 @@ package com.android.server.integrity.parser; import android.content.integrity.Rule; -import java.io.InputStream; import java.util.List; /** A helper class to parse rules into the {@link Rule} model. */ @@ -28,6 +27,6 @@ public interface RuleParser { List<Rule> parse(byte[] ruleBytes) throws RuleParseException; /** Parse rules from an input stream. */ - List<Rule> parse(InputStream inputStream, List<RuleIndexRange> ruleIndexRanges) + List<Rule> parse(RandomAccessObject randomAccessObject, List<RuleIndexRange> ruleIndexRanges) throws RuleParseException; } diff --git a/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java b/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java index 497be8424286..53b0c2e59453 100644 --- a/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java +++ b/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java @@ -26,7 +26,6 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; -import java.io.InputStream; import java.io.StringReader; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -62,11 +61,13 @@ public final class RuleXmlParser implements RuleParser { } @Override - public List<Rule> parse(InputStream inputStream, List<RuleIndexRange> indexRanges) + public List<Rule> parse(RandomAccessObject randomAccessObject, List<RuleIndexRange> indexRanges) throws RuleParseException { try { XmlPullParser xmlPullParser = Xml.newPullParser(); - xmlPullParser.setInput(inputStream, StandardCharsets.UTF_8.name()); + xmlPullParser.setInput( + new RandomAccessInputStream(randomAccessObject), + StandardCharsets.UTF_8.name()); return parseRules(xmlPullParser); } catch (Exception e) { throw new RuleParseException(e.getMessage(), e); diff --git a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java index d3588d38d976..6afadbad2054 100644 --- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java +++ b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java @@ -52,7 +52,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.TreeMap; +import java.util.stream.Collectors; /** A helper class to serialize rules from the {@link Rule} model to Binary representation. */ public class RuleBinarySerializer implements RuleSerializer { @@ -80,67 +80,69 @@ public class RuleBinarySerializer implements RuleSerializer { throws RuleSerializeException { try { // Determine the indexing groups and the order of the rules within each indexed group. - Map<Integer, TreeMap<String, List<Rule>>> indexedRules = + Map<Integer, Map<String, List<Rule>>> indexedRules = RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets(rules); // Serialize the rules. ByteTrackedOutputStream ruleFileByteTrackedOutputStream = new ByteTrackedOutputStream(rulesFileOutputStream); serializeRuleFileMetadata(formatVersion, ruleFileByteTrackedOutputStream); - Map<String, Integer> packageNameIndexes = - serializeRuleList(indexedRules.get(PACKAGE_NAME_INDEXED), + LinkedHashMap<String, Integer> packageNameIndexes = + serializeRuleList( + indexedRules.get(PACKAGE_NAME_INDEXED), ruleFileByteTrackedOutputStream); - Map<String, Integer> appCertificateIndexes = - serializeRuleList(indexedRules.get(APP_CERTIFICATE_INDEXED), - ruleFileByteTrackedOutputStream); - Map<String, Integer> unindexedRulesIndexes = - serializeRuleList(indexedRules.get(NOT_INDEXED), + LinkedHashMap<String, Integer> appCertificateIndexes = + serializeRuleList( + indexedRules.get(APP_CERTIFICATE_INDEXED), ruleFileByteTrackedOutputStream); + LinkedHashMap<String, Integer> unindexedRulesIndexes = + serializeRuleList( + indexedRules.get(NOT_INDEXED), ruleFileByteTrackedOutputStream); // Serialize their indexes. - BitOutputStream indexingBitOutputStream = new BitOutputStream(); - serializeIndexGroup(packageNameIndexes, indexingBitOutputStream, /* isIndexed= */true); - serializeIndexGroup(appCertificateIndexes, indexingBitOutputStream, /* isIndexed= */ - true); - serializeIndexGroup(unindexedRulesIndexes, indexingBitOutputStream, /* isIndexed= */ - false); - indexingFileOutputStream.write(indexingBitOutputStream.toByteArray()); + BitOutputStream indexingBitOutputStream = new BitOutputStream(indexingFileOutputStream); + serializeIndexGroup(packageNameIndexes, indexingBitOutputStream, /* isIndexed= */ true); + serializeIndexGroup( + appCertificateIndexes, indexingBitOutputStream, /* isIndexed= */ true); + serializeIndexGroup( + unindexedRulesIndexes, indexingBitOutputStream, /* isIndexed= */ false); + indexingBitOutputStream.flush(); } catch (Exception e) { throw new RuleSerializeException(e.getMessage(), e); } } - private void serializeRuleFileMetadata(Optional<Integer> formatVersion, - ByteTrackedOutputStream outputStream) + private void serializeRuleFileMetadata( + Optional<Integer> formatVersion, ByteTrackedOutputStream outputStream) throws IOException { int formatVersionValue = formatVersion.orElse(DEFAULT_FORMAT_VERSION); - BitOutputStream bitOutputStream = new BitOutputStream(); + BitOutputStream bitOutputStream = new BitOutputStream(outputStream); bitOutputStream.setNext(FORMAT_VERSION_BITS, formatVersionValue); - outputStream.write(bitOutputStream.toByteArray()); + bitOutputStream.flush(); } - private Map<String, Integer> serializeRuleList(TreeMap<String, List<Rule>> rulesMap, - ByteTrackedOutputStream outputStream) + private LinkedHashMap<String, Integer> serializeRuleList( + Map<String, List<Rule>> rulesMap, ByteTrackedOutputStream outputStream) throws IOException { - Preconditions.checkArgument(rulesMap != null, - "serializeRuleList should never be called with null rule list."); - - BitOutputStream bitOutputStream = new BitOutputStream(); - Map<String, Integer> indexMapping = new LinkedHashMap(); - int indexTracker = 0; + Preconditions.checkArgument( + rulesMap != null, "serializeRuleList should never be called with null rule list."); + BitOutputStream bitOutputStream = new BitOutputStream(outputStream); + LinkedHashMap<String, Integer> indexMapping = new LinkedHashMap(); indexMapping.put(START_INDEXING_KEY, outputStream.getWrittenBytesCount()); - for (Map.Entry<String, List<Rule>> entry : rulesMap.entrySet()) { + + List<String> sortedKeys = rulesMap.keySet().stream().sorted().collect(Collectors.toList()); + int indexTracker = 0; + for (String key : sortedKeys) { if (indexTracker >= INDEXING_BLOCK_SIZE) { - indexMapping.put(entry.getKey(), outputStream.getWrittenBytesCount()); + indexMapping.put(key, outputStream.getWrittenBytesCount()); indexTracker = 0; } - for (Rule rule : entry.getValue()) { - bitOutputStream.clear(); + for (Rule rule : rulesMap.get(key)) { serializeRule(rule, bitOutputStream); - outputStream.write(bitOutputStream.toByteArray()); + bitOutputStream.flush(); indexTracker++; } } @@ -149,7 +151,7 @@ public class RuleBinarySerializer implements RuleSerializer { return indexMapping; } - private void serializeRule(Rule rule, BitOutputStream bitOutputStream) { + private void serializeRule(Rule rule, BitOutputStream bitOutputStream) throws IOException { if (rule == null) { throw new IllegalArgumentException("Null rule can not be serialized"); } @@ -164,7 +166,8 @@ public class RuleBinarySerializer implements RuleSerializer { bitOutputStream.setNext(); } - private void serializeFormula(Formula formula, BitOutputStream bitOutputStream) { + private void serializeFormula(Formula formula, BitOutputStream bitOutputStream) + throws IOException { if (formula instanceof AtomicFormula) { serializeAtomicFormula((AtomicFormula) formula, bitOutputStream); } else if (formula instanceof CompoundFormula) { @@ -176,7 +179,7 @@ public class RuleBinarySerializer implements RuleSerializer { } private void serializeCompoundFormula( - CompoundFormula compoundFormula, BitOutputStream bitOutputStream) { + CompoundFormula compoundFormula, BitOutputStream bitOutputStream) throws IOException { if (compoundFormula == null) { throw new IllegalArgumentException("Null compound formula can not be serialized"); } @@ -190,7 +193,7 @@ public class RuleBinarySerializer implements RuleSerializer { } private void serializeAtomicFormula( - AtomicFormula atomicFormula, BitOutputStream bitOutputStream) { + AtomicFormula atomicFormula, BitOutputStream bitOutputStream) throws IOException { if (atomicFormula == null) { throw new IllegalArgumentException("Null atomic formula can not be serialized"); } @@ -222,11 +225,12 @@ public class RuleBinarySerializer implements RuleSerializer { } private void serializeIndexGroup( - Map<String, Integer> indexes, BitOutputStream bitOutputStream, boolean isIndexed) { - + LinkedHashMap<String, Integer> indexes, + BitOutputStream bitOutputStream, + boolean isIndexed) + throws IOException { // Output the starting location of this indexing group. - serializeStringValue(START_INDEXING_KEY, /* isHashedValue= */false, - bitOutputStream); + serializeStringValue(START_INDEXING_KEY, /* isHashedValue= */ false, bitOutputStream); serializeIntValue(indexes.get(START_INDEXING_KEY), bitOutputStream); // If the group is indexed, output the locations of the indexes. @@ -234,8 +238,8 @@ public class RuleBinarySerializer implements RuleSerializer { for (Map.Entry<String, Integer> entry : indexes.entrySet()) { if (!entry.getKey().equals(START_INDEXING_KEY) && !entry.getKey().equals(END_INDEXING_KEY)) { - serializeStringValue(entry.getKey(), /* isHashedValue= */false, - bitOutputStream); + serializeStringValue( + entry.getKey(), /* isHashedValue= */ false, bitOutputStream); serializeIntValue(entry.getValue(), bitOutputStream); } } @@ -244,13 +248,11 @@ public class RuleBinarySerializer implements RuleSerializer { // Output the end location of this indexing group. serializeStringValue(END_INDEXING_KEY, /*isHashedValue= */ false, bitOutputStream); serializeIntValue(indexes.get(END_INDEXING_KEY), bitOutputStream); - - // This dummy bit is set for fixing the padding issue. songpan@ will fix it and remove it. - bitOutputStream.setNext(); } private void serializeStringValue( - String value, boolean isHashedValue, BitOutputStream bitOutputStream) { + String value, boolean isHashedValue, BitOutputStream bitOutputStream) + throws IOException { if (value == null) { throw new IllegalArgumentException("String value can not be null."); } @@ -263,11 +265,12 @@ public class RuleBinarySerializer implements RuleSerializer { } } - private void serializeIntValue(int value, BitOutputStream bitOutputStream) { + private void serializeIntValue(int value, BitOutputStream bitOutputStream) throws IOException { bitOutputStream.setNext(/* numOfBits= */ 32, value); } - private void serializeBooleanValue(boolean value, BitOutputStream bitOutputStream) { + private void serializeBooleanValue(boolean value, BitOutputStream bitOutputStream) + throws IOException { bitOutputStream.setNext(value); } diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java index dd871e2bbe6c..2cbd4ede5214 100644 --- a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java +++ b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java @@ -28,6 +28,8 @@ class RuleIndexingDetails { static final int PACKAGE_NAME_INDEXED = 1; static final int APP_CERTIFICATE_INDEXED = 2; + static final String DEFAULT_RULE_KEY = "N/A"; + /** Represents which indexed file the rule should be located. */ @IntDef( value = { @@ -45,7 +47,7 @@ class RuleIndexingDetails { /** Constructor without a ruleKey for {@code NOT_INDEXED}. */ RuleIndexingDetails(@IndexType int indexType) { this.mIndexType = indexType; - this.mRuleKey = null; + this.mRuleKey = DEFAULT_RULE_KEY; } /** Constructor with a ruleKey for indexed rules. */ diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java index cbc365e2c250..7d9a90188983 100644 --- a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java +++ b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java @@ -30,30 +30,27 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.TreeMap; /** A helper class for identifying the indexing type and key of a given rule. */ class RuleIndexingDetailsIdentifier { - private static final String DEFAULT_RULE_KEY = "N/A"; - /** * Splits a given rule list into three indexing categories. Each rule category is returned as a * TreeMap that is sorted by their indexing keys -- where keys correspond to package name for * PACKAGE_NAME_INDEXED rules, app certificate for APP_CERTIFICATE_INDEXED rules and N/A for * NOT_INDEXED rules. */ - public static Map<Integer, TreeMap<String, List<Rule>>> splitRulesIntoIndexBuckets( + public static Map<Integer, Map<String, List<Rule>>> splitRulesIntoIndexBuckets( List<Rule> rules) { if (rules == null) { throw new IllegalArgumentException( "Index buckets cannot be created for null rule list."); } - Map<Integer, TreeMap<String, List<Rule>>> typeOrganizedRuleMap = new HashMap(); - typeOrganizedRuleMap.put(NOT_INDEXED, new TreeMap()); - typeOrganizedRuleMap.put(PACKAGE_NAME_INDEXED, new TreeMap()); - typeOrganizedRuleMap.put(APP_CERTIFICATE_INDEXED, new TreeMap()); + Map<Integer, Map<String, List<Rule>>> typeOrganizedRuleMap = new HashMap(); + typeOrganizedRuleMap.put(NOT_INDEXED, new HashMap()); + typeOrganizedRuleMap.put(PACKAGE_NAME_INDEXED, new HashMap<>()); + typeOrganizedRuleMap.put(APP_CERTIFICATE_INDEXED, new HashMap<>()); // Split the rules into the appropriate indexed pattern. The Tree Maps help us to keep the // entries sorted by their index key. @@ -66,21 +63,14 @@ class RuleIndexingDetailsIdentifier { String.format("Malformed rule identified. [%s]", rule.toString())); } - String ruleKey = - indexingDetails.getIndexType() != NOT_INDEXED - ? indexingDetails.getRuleKey() - : DEFAULT_RULE_KEY; + int ruleIndexType = indexingDetails.getIndexType(); + String ruleKey = indexingDetails.getRuleKey(); - if (!typeOrganizedRuleMap.get(indexingDetails.getIndexType()).containsKey(ruleKey)) { - typeOrganizedRuleMap - .get(indexingDetails.getIndexType()) - .put(ruleKey, new ArrayList()); + if (!typeOrganizedRuleMap.get(ruleIndexType).containsKey(ruleKey)) { + typeOrganizedRuleMap.get(ruleIndexType).put(ruleKey, new ArrayList()); } - typeOrganizedRuleMap - .get(indexingDetails.getIndexType()) - .get(ruleKey) - .add(rule); + typeOrganizedRuleMap.get(ruleIndexType).get(ruleKey).add(rule); } return typeOrganizedRuleMap; diff --git a/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java index 4194432375b8..8f164e645434 100644 --- a/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java +++ b/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java @@ -35,7 +35,7 @@ import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.TreeMap; +import java.util.stream.Collectors; /** A helper class to serialize rules from the {@link Rule} model to Xml representation. */ public class RuleXmlSerializer implements RuleSerializer { @@ -90,7 +90,7 @@ public class RuleXmlSerializer implements RuleSerializer { throws RuleSerializeException { try { // Determine the indexing groups and the order of the rules within each indexed group. - Map<Integer, TreeMap<String, List<Rule>>> indexedRules = + Map<Integer, Map<String, List<Rule>>> indexedRules = RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets(rules); // Write the XML formatted rules in order. @@ -107,11 +107,12 @@ public class RuleXmlSerializer implements RuleSerializer { } } - private void serializeRuleList(TreeMap<String, List<Rule>> rulesMap, - XmlSerializer xmlSerializer) + private void serializeRuleList(Map<String, List<Rule>> rulesMap, XmlSerializer xmlSerializer) throws IOException { - for (Map.Entry<String, List<Rule>> entry : rulesMap.entrySet()) { - for (Rule rule : entry.getValue()) { + List<String> sortedKeyList = + rulesMap.keySet().stream().sorted().collect(Collectors.toList()); + for (String key : sortedKeyList) { + for (Rule rule : rulesMap.get(key)) { serializeRule(rule, xmlSerializer); } } diff --git a/services/core/java/com/android/server/location/UserInfoStore.java b/services/core/java/com/android/server/location/UserInfoStore.java index 550f51c7de58..f282ed26a831 100644 --- a/services/core/java/com/android/server/location/UserInfoStore.java +++ b/services/core/java/com/android/server/location/UserInfoStore.java @@ -24,6 +24,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.UserInfo; +import android.os.Binder; import android.os.Build; import android.os.UserHandle; import android.os.UserManager; @@ -152,7 +153,7 @@ public class UserInfoStore { // this intent is only sent to the current user if (mCachedParentUserId == mCurrentUserId) { mCachedParentUserId = UserHandle.USER_NULL; - mCachedProfileUserIds = null; + mCachedProfileUserIds = new int[]{UserHandle.USER_NULL}; } } @@ -185,16 +186,21 @@ public class UserInfoStore { } else { Preconditions.checkState(mUserManager != null); - UserInfo userInfo = mUserManager.getProfileParent(userId); - if (userInfo != null) { - parentUserId = userInfo.id; - } else { - // getProfileParent() returns null if the userId is already the parent... - parentUserId = userId; - } + long identity = Binder.clearCallingIdentity(); + try { + UserInfo userInfo = mUserManager.getProfileParent(userId); + if (userInfo != null) { + parentUserId = userInfo.id; + } else { + // getProfileParent() returns null if the userId is already the parent... + parentUserId = userId; + } - // force profiles into cache - getProfileUserIdsForParentUser(parentUserId); + // force profiles into cache + getProfileUserIdsForParentUser(parentUserId); + } finally { + Binder.restoreCallingIdentity(identity); + } } return parentUserId; @@ -204,13 +210,24 @@ public class UserInfoStore { private int[] getProfileUserIdsForParentUser(@UserIdInt int parentUserId) { Preconditions.checkState(mUserManager != null); + // only assert on debug builds as this is a more expensive check if (Build.IS_DEBUGGABLE) { - Preconditions.checkArgument(mUserManager.getProfileParent(parentUserId) == null); + long identity = Binder.clearCallingIdentity(); + try { + Preconditions.checkArgument(mUserManager.getProfileParent(parentUserId) == null); + } finally { + Binder.restoreCallingIdentity(identity); + } } if (parentUserId != mCachedParentUserId) { - mCachedParentUserId = parentUserId; - mCachedProfileUserIds = mUserManager.getProfileIdsWithDisabled(parentUserId); + long identity = Binder.clearCallingIdentity(); + try { + mCachedParentUserId = parentUserId; + mCachedProfileUserIds = mUserManager.getProfileIdsWithDisabled(parentUserId); + } finally { + Binder.restoreCallingIdentity(identity); + } } return mCachedProfileUserIds; diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java index 0f8561e5afd9..4943c25b1f18 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java @@ -24,6 +24,7 @@ import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTE import android.app.ActivityManager; import android.app.admin.PasswordMetrics; import android.os.ShellCommand; +import android.text.TextUtils; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternUtils.RequestThrottledException; @@ -195,6 +196,9 @@ class LockSettingsShellCommand extends ShellCommand { } private LockscreenCredential getOldCredential() { + if (TextUtils.isEmpty(mOld)) { + return LockscreenCredential.createNone(); + } if (mLockPatternUtils.isLockPasswordEnabled(mCurrentUserId)) { final int quality = mLockPatternUtils.getKeyguardStoredPasswordQuality(mCurrentUserId); if (LockPatternUtils.isQualityAlphabeticPassword(quality)) { @@ -202,12 +206,15 @@ class LockSettingsShellCommand extends ShellCommand { } else { return LockscreenCredential.createPin(mOld); } - } else if (mLockPatternUtils.isLockPatternEnabled(mCurrentUserId)) { + } + if (mLockPatternUtils.isLockPatternEnabled(mCurrentUserId)) { return LockscreenCredential.createPattern(LockPatternUtils.byteArrayToPattern( mOld.getBytes())); - } else { - return LockscreenCredential.createNone(); } + // User supplied some old credential but the device has neither password nor pattern, + // so just return a password credential (and let it be rejected during LSS verification) + return LockscreenCredential.createPassword(mOld); + } private boolean runSetPattern() { diff --git a/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java b/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java index 1d391775e550..b0bccb8eb38b 100644 --- a/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java +++ b/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java @@ -172,13 +172,14 @@ class AudioPlayerStateMonitor { */ public void cleanUpAudioPlaybackUids(int mediaButtonSessionUid) { synchronized (mLock) { - int userId = UserHandle.getUserId(mediaButtonSessionUid); + int userId = UserHandle.getUserHandleForUid(mediaButtonSessionUid).getIdentifier(); for (int i = mSortedAudioPlaybackClientUids.size() - 1; i >= 0; i--) { if (mSortedAudioPlaybackClientUids.get(i) == mediaButtonSessionUid) { break; } int uid = mSortedAudioPlaybackClientUids.get(i); - if (userId == UserHandle.getUserId(uid) && !isPlaybackActive(uid)) { + if (userId == UserHandle.getUserHandleForUid(uid).getIdentifier() + && !isPlaybackActive(uid)) { // Clean up unnecessary UIDs. // It doesn't need to be managed profile aware because it's just to prevent // the list from increasing indefinitely. The media button session updating diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java index 5191833f367e..b67335ab82bc 100644 --- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java +++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java @@ -306,14 +306,17 @@ class BluetoothRouteProvider { } } else if (state == BluetoothProfile.STATE_DISCONNECTING || state == BluetoothProfile.STATE_DISCONNECTED) { - btRoute.connectedProfiles.delete(profile); - if (btRoute.connectedProfiles.size() == 0) { - mBluetoothRoutes.remove(device.getAddress()); - if (mActiveDevice != null - && TextUtils.equals(mActiveDevice.getAddress(), device.getAddress())) { - mActiveDevice = null; + if (btRoute != null) { + btRoute.connectedProfiles.delete(profile); + if (btRoute.connectedProfiles.size() == 0) { + mBluetoothRoutes.remove(device.getAddress()); + if (mActiveDevice != null + && TextUtils.equals(mActiveDevice.getAddress(), + device.getAddress())) { + mActiveDevice = null; + } + notifyBluetoothRoutesUpdated(); } - notifyBluetoothRoutesUpdated(); } } } diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java index 0d315cd6863d..b186771031fc 100644 --- a/services/core/java/com/android/server/media/MediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java @@ -23,18 +23,22 @@ import android.content.Intent; import android.media.MediaRoute2ProviderInfo; import android.media.RoutingSessionInfo; +import com.android.internal.annotations.GuardedBy; + import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Objects; abstract class MediaRoute2Provider { final ComponentName mComponentName; final String mUniqueId; + final Object mLock = new Object(); Callback mCallback; private volatile MediaRoute2ProviderInfo mProviderInfo; - private volatile List<RoutingSessionInfo> mSessionInfos = Collections.emptyList(); + + @GuardedBy("mLock") + final List<RoutingSessionInfo> mSessionInfos = new ArrayList<>(); MediaRoute2Provider(@NonNull ComponentName componentName) { mComponentName = Objects.requireNonNull(componentName, "Component name must not be null."); @@ -45,8 +49,7 @@ abstract class MediaRoute2Provider { mCallback = callback; } - public abstract void requestCreateSession(String packageName, String routeId, - String routeType, long requestId); + public abstract void requestCreateSession(String packageName, String routeId, long requestId); public abstract void releaseSession(String sessionId); public abstract void selectRoute(String sessionId, String routeId); @@ -69,11 +72,12 @@ abstract class MediaRoute2Provider { @NonNull public List<RoutingSessionInfo> getSessionInfos() { - return mSessionInfos; + synchronized (mLock) { + return mSessionInfos; + } } - void setProviderState(MediaRoute2ProviderInfo providerInfo, - List<RoutingSessionInfo> sessionInfos) { + void setProviderState(MediaRoute2ProviderInfo providerInfo) { if (providerInfo == null) { mProviderInfo = null; } else { @@ -81,14 +85,6 @@ abstract class MediaRoute2Provider { .setUniqueId(mUniqueId) .build(); } - List<RoutingSessionInfo> sessionInfoWithProviderId = new ArrayList<RoutingSessionInfo>(); - for (RoutingSessionInfo sessionInfo : sessionInfos) { - sessionInfoWithProviderId.add( - new RoutingSessionInfo.Builder(sessionInfo) - .setProviderId(mUniqueId) - .build()); - } - mSessionInfos = sessionInfoWithProviderId; } void notifyProviderState() { @@ -97,9 +93,8 @@ abstract class MediaRoute2Provider { } } - void setAndNotifyProviderState(MediaRoute2ProviderInfo providerInfo, - List<RoutingSessionInfo> sessionInfos) { - setProviderState(providerInfo, sessionInfos); + void setAndNotifyProviderState(MediaRoute2ProviderInfo providerInfo) { + setProviderState(providerInfo); notifyProviderState(); } @@ -112,10 +107,9 @@ abstract class MediaRoute2Provider { void onProviderStateChanged(@Nullable MediaRoute2Provider provider); void onSessionCreated(@NonNull MediaRoute2Provider provider, @Nullable RoutingSessionInfo sessionInfo, long requestId); - // TODO: Remove this when MediaRouter2ServiceImpl notifies clients of session changes. - void onSessionInfoChanged(@NonNull MediaRoute2Provider provider, + void onSessionCreationFailed(@NonNull MediaRoute2Provider provider, long requestId); + void onSessionUpdated(@NonNull MediaRoute2Provider provider, @NonNull RoutingSessionInfo sessionInfo); - // TODO: Call this when service actually notifies of session release. void onSessionReleased(@NonNull MediaRoute2Provider provider, @NonNull RoutingSessionInfo sessionInfo); } diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java index c0ad46f5fa06..4b992be46792 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java @@ -17,7 +17,6 @@ package com.android.server.media; import android.annotation.NonNull; -import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -37,8 +36,6 @@ import android.util.Slog; import java.io.PrintWriter; import java.lang.ref.WeakReference; -import java.util.Collections; -import java.util.List; import java.util.Objects; /** @@ -76,11 +73,9 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } @Override - public void requestCreateSession(String packageName, String routeId, String routeType, - long requestId) { + public void requestCreateSession(String packageName, String routeId, long requestId) { if (mConnectionReady) { - mActiveConnection.requestCreateSession(packageName, routeId, routeType, - requestId); + mActiveConnection.requestCreateSession(packageName, routeId, requestId); updateBinding(); } } @@ -270,35 +265,69 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } private void onProviderStateUpdated(Connection connection, - MediaRoute2ProviderInfo providerInfo, List<RoutingSessionInfo> sessionInfos) { + MediaRoute2ProviderInfo providerInfo) { if (mActiveConnection != connection) { return; } if (DEBUG) { Slog.d(TAG, this + ": State changed "); } - setAndNotifyProviderState(providerInfo, sessionInfos); + setAndNotifyProviderState(providerInfo); } - private void onSessionCreated(Connection connection, @Nullable RoutingSessionInfo sessionInfo, + private void onSessionCreated(Connection connection, RoutingSessionInfo sessionInfo, long requestId) { if (mActiveConnection != connection) { return; } - if (sessionInfo != null) { - sessionInfo = new RoutingSessionInfo.Builder(sessionInfo) - .setProviderId(getUniqueId()) - .build(); + + if (sessionInfo == null) { + Slog.w(TAG, "onSessionCreated: Ignoring null sessionInfo sent from " + mComponentName); + return; } + + sessionInfo = new RoutingSessionInfo.Builder(sessionInfo) + .setProviderId(getUniqueId()) + .build(); + + boolean duplicateSessionAlreadyExists = false; + synchronized (mLock) { + for (int i = 0; i < mSessionInfos.size(); i++) { + if (mSessionInfos.get(i).getId().equals(sessionInfo.getId())) { + duplicateSessionAlreadyExists = true; + break; + } + } + mSessionInfos.add(sessionInfo); + } + + if (duplicateSessionAlreadyExists) { + Slog.w(TAG, "onSessionCreated: Duplicate session already exists. Ignoring."); + return; + } + mCallback.onSessionCreated(this, sessionInfo, requestId); } - private void onSessionInfoChanged(Connection connection, RoutingSessionInfo sessionInfo) { + private void onSessionCreationFailed(Connection connection, long requestId) { + if (mActiveConnection != connection) { + return; + } + + if (requestId == MediaRoute2ProviderService.REQUEST_ID_UNKNOWN) { + Slog.w(TAG, "onSessionCreationFailed: Ignoring requestId REQUEST_ID_UNKNOWN"); + return; + } + + mCallback.onSessionCreationFailed(this, requestId); + } + + private void onSessionUpdated(Connection connection, RoutingSessionInfo sessionInfo) { if (mActiveConnection != connection) { return; } if (sessionInfo == null) { - Slog.w(TAG, "onSessionInfoChanged: Ignoring null sessionInfo sent from " + Slog.w(TAG, "onSessionUpdated: Ignoring null sessionInfo sent from " + mComponentName); return; } @@ -307,7 +336,55 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv .setProviderId(getUniqueId()) .build(); - mCallback.onSessionInfoChanged(this, sessionInfo); + boolean found = false; + synchronized (mLock) { + for (int i = 0; i < mSessionInfos.size(); i++) { + if (mSessionInfos.get(i).getId().equals(sessionInfo.getId())) { + mSessionInfos.set(i, sessionInfo); + found = true; + break; + } + } + } + + if (!found) { + Slog.w(TAG, "onSessionUpdated: Matching session info not found"); + return; + } + + mCallback.onSessionUpdated(this, sessionInfo); + } + + private void onSessionReleased(Connection connection, RoutingSessionInfo sessionInfo) { + if (mActiveConnection != connection) { + return; + } + if (sessionInfo == null) { + Slog.w(TAG, "onSessionReleased: Ignoring null sessionInfo sent from " + mComponentName); + return; + } + + sessionInfo = new RoutingSessionInfo.Builder(sessionInfo) + .setProviderId(getUniqueId()) + .build(); + + boolean found = false; + synchronized (mLock) { + for (int i = 0; i < mSessionInfos.size(); i++) { + if (mSessionInfos.get(i).getId().equals(sessionInfo.getId())) { + mSessionInfos.remove(i); + found = true; + break; + } + } + } + + if (!found) { + Slog.w(TAG, "onSessionReleased: Matching session info not found"); + return; + } + + mCallback.onSessionReleased(this, sessionInfo); } private void disconnect() { @@ -315,7 +392,7 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv mConnectionReady = false; mActiveConnection.dispose(); mActiveConnection = null; - setAndNotifyProviderState(null, Collections.emptyList()); + setAndNotifyProviderState(null); } } @@ -350,11 +427,9 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv mClient.dispose(); } - public void requestCreateSession(String packageName, String routeId, String routeType, - long requestId) { + public void requestCreateSession(String packageName, String routeId, long requestId) { try { - mProvider.requestCreateSession(packageName, routeId, - routeType, requestId); + mProvider.requestCreateSession(packageName, routeId, requestId); } catch (RemoteException ex) { Slog.e(TAG, "Failed to deliver request to create a session.", ex); } @@ -421,19 +496,24 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv mHandler.post(() -> onConnectionDied(Connection.this)); } - void postProviderStateUpdated(MediaRoute2ProviderInfo providerInfo, - List<RoutingSessionInfo> sessionInfos) { - mHandler.post(() -> onProviderStateUpdated(Connection.this, - providerInfo, sessionInfos)); + void postProviderStateUpdated(MediaRoute2ProviderInfo providerInfo) { + mHandler.post(() -> onProviderStateUpdated(Connection.this, providerInfo)); + } + + void postSessionCreated(RoutingSessionInfo sessionInfo, long requestId) { + mHandler.post(() -> onSessionCreated(Connection.this, sessionInfo, requestId)); + } + + void postSessionCreationFailed(long requestId) { + mHandler.post(() -> onSessionCreationFailed(Connection.this, requestId)); } - void postSessionCreated(@Nullable RoutingSessionInfo sessionInfo, long requestId) { - mHandler.post(() -> onSessionCreated(Connection.this, sessionInfo, - requestId)); + void postSessionUpdated(RoutingSessionInfo sessionInfo) { + mHandler.post(() -> onSessionUpdated(Connection.this, sessionInfo)); } - void postSessionInfoChanged(RoutingSessionInfo sessionInfo) { - mHandler.post(() -> onSessionInfoChanged(Connection.this, sessionInfo)); + void postSessionReleased(RoutingSessionInfo sessionInfo) { + mHandler.post(() -> onSessionReleased(Connection.this, sessionInfo)); } } @@ -449,16 +529,15 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } @Override - public void updateState(MediaRoute2ProviderInfo providerInfo, - List<RoutingSessionInfo> sessionInfos) { + public void updateState(MediaRoute2ProviderInfo providerInfo) { Connection connection = mConnectionRef.get(); if (connection != null) { - connection.postProviderStateUpdated(providerInfo, sessionInfos); + connection.postProviderStateUpdated(providerInfo); } } @Override - public void notifySessionCreated(@Nullable RoutingSessionInfo sessionInfo, long requestId) { + public void notifySessionCreated(RoutingSessionInfo sessionInfo, long requestId) { Connection connection = mConnectionRef.get(); if (connection != null) { connection.postSessionCreated(sessionInfo, requestId); @@ -466,10 +545,26 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } @Override - public void notifySessionInfoChanged(RoutingSessionInfo sessionInfo) { + public void notifySessionCreationFailed(long requestId) { + Connection connection = mConnectionRef.get(); + if (connection != null) { + connection.postSessionCreationFailed(requestId); + } + } + + @Override + public void notifySessionUpdated(RoutingSessionInfo sessionInfo) { + Connection connection = mConnectionRef.get(); + if (connection != null) { + connection.postSessionUpdated(sessionInfo); + } + } + + @Override + public void notifySessionReleased(RoutingSessionInfo sessionInfo) { Connection connection = mConnectionRef.get(); if (connection != null) { - connection.postSessionInfoChanged(sessionInfo); + connection.postSessionReleased(sessionInfo); } } } diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index c48c90db30d1..45d50b33357e 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -22,7 +22,6 @@ import static android.media.MediaRouter2Utils.getProviderId; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import android.annotation.NonNull; -import android.annotation.Nullable; import android.app.ActivityManager; import android.content.Context; import android.content.Intent; @@ -31,6 +30,7 @@ import android.media.IMediaRouter2Client; import android.media.IMediaRouter2Manager; import android.media.MediaRoute2Info; import android.media.MediaRoute2ProviderInfo; +import android.media.MediaRoute2ProviderService; import android.media.RouteDiscoveryPreference; import android.media.RoutingSessionInfo; import android.os.Binder; @@ -90,7 +90,7 @@ class MediaRouter2ServiceImpl { @NonNull public List<MediaRoute2Info> getSystemRoutes() { final int uid = Binder.getCallingUid(); - final int userId = UserHandle.getUserId(uid); + final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier(); final long token = Binder.clearCallingIdentity(); try { @@ -117,7 +117,7 @@ class MediaRouter2ServiceImpl { final int uid = Binder.getCallingUid(); final int pid = Binder.getCallingPid(); - final int userId = UserHandle.getUserId(uid); + final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier(); final boolean trusted = mContext.checkCallingOrSelfPermission( android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) == PackageManager.PERMISSION_GRANTED; @@ -152,7 +152,7 @@ class MediaRouter2ServiceImpl { final int uid = Binder.getCallingUid(); final int pid = Binder.getCallingPid(); - final int userId = UserHandle.getUserId(uid); + final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier(); final long token = Binder.clearCallingIdentity(); try { @@ -178,18 +178,14 @@ class MediaRouter2ServiceImpl { } public void requestCreateSession(IMediaRouter2Client client, MediaRoute2Info route, - String routeFeature, int requestId) { + int requestId) { Objects.requireNonNull(client, "client must not be null"); Objects.requireNonNull(route, "route must not be null"); - if (TextUtils.isEmpty(routeFeature)) { - throw new IllegalArgumentException("routeFeature must not be empty"); - } final long token = Binder.clearCallingIdentity(); - try { synchronized (mLock) { - requestCreateSessionLocked(client, route, routeFeature, requestId); + requestCreateSessionLocked(client, route, requestId); } } finally { Binder.restoreCallingIdentity(token); @@ -450,7 +446,7 @@ class MediaRouter2ServiceImpl { } private void requestCreateSessionLocked(@NonNull IMediaRouter2Client client, - @NonNull MediaRoute2Info route, @NonNull String routeFeature, long requestId) { + @NonNull MediaRoute2Info route, long requestId) { final IBinder binder = client.asBinder(); final Client2Record clientRecord = mAllClientRecords.get(binder); @@ -462,8 +458,7 @@ class MediaRouter2ServiceImpl { if (clientRecord != null) { clientRecord.mUserRecord.mHandler.sendMessage( obtainMessage(UserHandler::requestCreateSessionOnHandler, - clientRecord.mUserRecord.mHandler, - clientRecord, route, routeFeature, requestId)); + clientRecord.mUserRecord.mHandler, clientRecord, route, requestId)); } } @@ -624,7 +619,7 @@ class MediaRouter2ServiceImpl { if (clientRecord != null && managerRecord.mTrusted) { //TODO: select route feature properly requestCreateSessionLocked(clientRecord.mClient, route, - route.getFeatures().get(0), uniqueRequestId); + uniqueRequestId); } } } @@ -876,13 +871,19 @@ class MediaRouter2ServiceImpl { @Override public void onSessionCreated(@NonNull MediaRoute2Provider provider, - @Nullable RoutingSessionInfo sessionInfo, long requestId) { + @NonNull RoutingSessionInfo sessionInfo, long requestId) { sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionCreatedOnHandler, this, provider, sessionInfo, requestId)); } @Override - public void onSessionInfoChanged(@NonNull MediaRoute2Provider provider, + public void onSessionCreationFailed(@NonNull MediaRoute2Provider provider, long requestId) { + sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionCreationFailedOnHandler, + this, provider, requestId)); + } + + @Override + public void onSessionUpdated(@NonNull MediaRoute2Provider provider, @NonNull RoutingSessionInfo sessionInfo) { sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionInfoChangedOnHandler, this, provider, sessionInfo)); @@ -979,7 +980,7 @@ class MediaRouter2ServiceImpl { } private void requestCreateSessionOnHandler(Client2Record clientRecord, - MediaRoute2Info route, String routeFeature, long requestId) { + MediaRoute2Info route, long requestId) { final MediaRoute2Provider provider = findProvider(route.getProviderId()); if (provider == null) { @@ -989,20 +990,13 @@ class MediaRouter2ServiceImpl { return; } - if (!route.getFeatures().contains(routeFeature)) { - Slog.w(TAG, "Ignoring session creation request since the given route=" + route - + " doesn't support the given feature=" + routeFeature); - notifySessionCreationFailed(clientRecord, toClientRequestId(requestId)); - return; - } - // TODO: Apply timeout for each request (How many seconds should we wait?) - SessionCreationRequest request = new SessionCreationRequest( - clientRecord, route, routeFeature, requestId); + SessionCreationRequest request = + new SessionCreationRequest(clientRecord, route, requestId); mSessionCreationRequests.add(request); provider.requestCreateSession(clientRecord.mPackageName, route.getOriginalId(), - routeFeature, requestId); + requestId); } private void selectRouteOnHandler(@NonNull Client2Record clientRecord, @@ -1132,7 +1126,14 @@ class MediaRouter2ServiceImpl { } private void onSessionCreatedOnHandler(@NonNull MediaRoute2Provider provider, - @Nullable RoutingSessionInfo sessionInfo, long requestId) { + @NonNull RoutingSessionInfo sessionInfo, long requestId) { + + if (requestId == MediaRoute2ProviderService.REQUEST_ID_UNKNOWN) { + // The session is created without any matching request. + // TODO: Tell managers for the session creation + return; + } + SessionCreationRequest matchingRequest = null; for (SessionCreationRequest request : mSessionCreationRequests) { @@ -1160,15 +1161,11 @@ class MediaRouter2ServiceImpl { } String originalRouteId = matchingRequest.mRoute.getId(); - String originalRouteFeature = matchingRequest.mRouteFeature; Client2Record client2Record = matchingRequest.mClientRecord; - if (!sessionInfo.getSelectedRoutes().contains(originalRouteId) - || !TextUtils.equals(originalRouteFeature, - sessionInfo.getRouteFeature())) { + if (!sessionInfo.getSelectedRoutes().contains(originalRouteId)) { Slog.w(TAG, "Created session doesn't match the original request." + " originalRouteId=" + originalRouteId - + ", originalRouteFeature=" + originalRouteFeature + ", requestId=" + requestId + ", sessionInfo=" + sessionInfo); notifySessionCreationFailed(matchingRequest.mClientRecord, toClientRequestId(requestId)); @@ -1182,6 +1179,30 @@ class MediaRouter2ServiceImpl { // TODO: Tell managers for the session creation } + private void onSessionCreationFailedOnHandler(@NonNull MediaRoute2Provider provider, + long requestId) { + SessionCreationRequest matchingRequest = null; + + for (SessionCreationRequest request : mSessionCreationRequests) { + if (request.mRequestId == requestId + && TextUtils.equals( + request.mRoute.getProviderId(), provider.getUniqueId())) { + matchingRequest = request; + break; + } + } + + if (matchingRequest == null) { + Slog.w(TAG, "Ignoring session creation failed result for unknown request. " + + "requestId=" + requestId); + return; + } + + mSessionCreationRequests.remove(matchingRequest); + notifySessionCreationFailed(matchingRequest.mClientRecord, + toClientRequestId(requestId)); + } + private void onSessionInfoChangedOnHandler(@NonNull MediaRoute2Provider provider, @NonNull RoutingSessionInfo sessionInfo) { @@ -1433,15 +1454,12 @@ class MediaRouter2ServiceImpl { final class SessionCreationRequest { public final Client2Record mClientRecord; public final MediaRoute2Info mRoute; - public final String mRouteFeature; public final long mRequestId; SessionCreationRequest(@NonNull Client2Record clientRecord, - @NonNull MediaRoute2Info route, - @NonNull String routeFeature, long requestId) { + @NonNull MediaRoute2Info route, long requestId) { mClientRecord = clientRecord; mRoute = route; - mRouteFeature = routeFeature; mRequestId = requestId; } } diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index b7aa484a3fc7..aad963688cc5 100644 --- a/services/core/java/com/android/server/media/MediaRouterService.java +++ b/services/core/java/com/android/server/media/MediaRouterService.java @@ -460,8 +460,8 @@ public final class MediaRouterService extends IMediaRouterService.Stub // Binder call @Override public void requestCreateSession(IMediaRouter2Client client, MediaRoute2Info route, - String routeType, int requestId) { - mService2.requestCreateSession(client, route, routeType, requestId); + int requestId) { + mService2.requestCreateSession(client, route, requestId); } // Binder call @@ -579,7 +579,8 @@ public final class MediaRouterService extends IMediaRouterService.Stub void restoreRoute(int uid) { ClientRecord clientRecord = null; synchronized (mLock) { - UserRecord userRecord = mUserRecords.get(UserHandle.getUserId(uid)); + UserRecord userRecord = mUserRecords.get( + UserHandle.getUserHandleForUid(uid).getIdentifier()); if (userRecord != null && userRecord.mClientRecords != null) { for (ClientRecord cr : userRecord.mClientRecords) { if (validatePackageName(uid, cr.mPackageName)) { diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java index f3241ee44569..b21d2e789555 100644 --- a/services/core/java/com/android/server/media/MediaSession2Record.java +++ b/services/core/java/com/android/server/media/MediaSession2Record.java @@ -77,7 +77,7 @@ public class MediaSession2Record implements MediaSessionRecordImpl { @Override public int getUserId() { - return UserHandle.getUserId(mSessionToken.getUid()); + return UserHandle.getUserHandleForUid(mSessionToken.getUid()).getIdentifier(); } @Override diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index df115d0f2773..190557122aa4 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -135,6 +135,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR private int mMaxVolume = 0; private int mCurrentVolume = 0; private int mOptimisticVolume = -1; + private String mVolumeControlId; // End volume handling fields private boolean mIsActive = false; @@ -714,7 +715,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_REMOTE) { int current = mOptimisticVolume != -1 ? mOptimisticVolume : mCurrentVolume; return new PlaybackInfo(mVolumeType, mVolumeControlType, mMaxVolume, current, - mAudioAttrs); + mAudioAttrs, mVolumeControlId); } volumeType = mVolumeType; attributes = mAudioAttrs; @@ -723,7 +724,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR int max = mAudioManager.getStreamMaxVolume(stream); int current = mAudioManager.getStreamVolume(stream); return new PlaybackInfo(volumeType, VolumeProvider.VOLUME_CONTROL_ABSOLUTE, max, - current, attributes); + current, attributes, null); } private final Runnable mClearOptimisticVolumeRunnable = new Runnable() { @@ -886,6 +887,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR synchronized (mLock) { typeChanged = mVolumeType == PlaybackInfo.PLAYBACK_TYPE_REMOTE; mVolumeType = PlaybackInfo.PLAYBACK_TYPE_LOCAL; + mVolumeControlId = null; if (attributes != null) { mAudioAttrs = attributes; } else { @@ -904,13 +906,15 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR } @Override - public void setPlaybackToRemote(int control, int max) throws RemoteException { + public void setPlaybackToRemote(int control, int max, String controlId) + throws RemoteException { boolean typeChanged; synchronized (mLock) { typeChanged = mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL; mVolumeType = PlaybackInfo.PLAYBACK_TYPE_REMOTE; mVolumeControlType = control; mMaxVolume = max; + mVolumeControlId = controlId; } if (typeChanged) { final long token = Binder.clearCallingIdentity(); diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index f71fb582e3ed..4a6fcdf73b90 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -166,7 +166,8 @@ public class MediaSessionService extends SystemService implements Monitor { } synchronized (mLock) { FullUserRecord user = getFullUserRecordLocked( - UserHandle.getUserId(config.getClientUid())); + UserHandle.getUserHandleForUid(config.getClientUid()) + .getIdentifier()); if (user != null) { user.mPriorityStack.updateMediaButtonSessionIfNeeded(); } @@ -472,8 +473,8 @@ public class MediaSessionService extends SystemService implements Monitor { if (mContext .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid) != PackageManager.PERMISSION_GRANTED - && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid), - resolvedUserId)) { + && !isEnabledNotificationListener(compName, + UserHandle.getUserHandleForUid(uid).getIdentifier(), resolvedUserId)) { throw new SecurityException("Missing permission to control media."); } } diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 961d3d01a67d..3759ba9743fa 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -96,8 +96,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { } @Override - public void requestCreateSession(String packageName, String routeId, String routeType, - long requestId) { + public void requestCreateSession(String packageName, String routeId, long requestId) { // Do nothing } @@ -169,7 +168,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { for (MediaRoute2Info route : mBluetoothRoutes) { builder.addRoute(route); } - setProviderState(builder.build(), Collections.emptyList()); + setProviderState(builder.build()); mHandler.post(() -> notifyProviderState()); } @@ -212,6 +211,6 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { for (MediaRoute2Info route : mBluetoothRoutes) { builder.addRoute(route); } - setAndNotifyProviderState(builder.build(), Collections.emptyList()); + setAndNotifyProviderState(builder.build()); } } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index d8a4655815ad..c518614a336f 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -161,7 +161,7 @@ import android.net.NetworkStack; import android.net.NetworkState; import android.net.NetworkStats; import android.net.NetworkTemplate; -import android.net.StringNetworkSpecifier; +import android.net.TelephonyNetworkSpecifier; import android.net.TrafficStats; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; @@ -2877,17 +2877,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } @Override - public void onTetheringChanged(String iface, boolean tethering) { - // No need to enforce permission because setRestrictBackground() will do it. - synchronized (mUidRulesFirstLock) { - if (mRestrictBackground && tethering) { - Log.d(TAG, "Tethering on (" + iface +"); disable Data Saver"); - setRestrictBackground(false); - } - } - } - - @Override public void setRestrictBackground(boolean restrictBackground) { Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "setRestrictBackground"); try { @@ -4522,7 +4511,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } case MSG_STATS_PROVIDER_LIMIT_REACHED: { mNetworkStats.forceUpdate(); + synchronized (mNetworkPoliciesSecondLock) { + // Some providers might hit the limit reached event prior to others. Thus, + // re-calculate and update interface quota for every provider is needed. + updateNetworkRulesNL(); updateNetworkEnabledNL(); updateNotificationsNL(); } @@ -4530,17 +4523,22 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } case MSG_LIMIT_REACHED: { final String iface = (String) msg.obj; - synchronized (mNetworkPoliciesSecondLock) { - if (mMeteredIfaces.contains(iface)) { - // force stats update to make sure we have - // numbers that caused alert to trigger. - mNetworkStats.forceUpdate(); - - updateNetworkEnabledNL(); - updateNotificationsNL(); + // fast return if not needed. + if (!mMeteredIfaces.contains(iface)) { + return true; } } + + // force stats update to make sure the service have the numbers that caused + // alert to trigger. + mNetworkStats.forceUpdate(); + + synchronized (mNetworkPoliciesSecondLock) { + updateNetworkRulesNL(); + updateNetworkEnabledNL(); + updateNotificationsNL(); + } return true; } case MSG_RESTRICT_BACKGROUND_CHANGED: { @@ -5283,16 +5281,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } private int parseSubId(NetworkState state) { - // TODO: moved to using a legitimate NetworkSpecifier instead of string parsing int subId = INVALID_SUBSCRIPTION_ID; if (state != null && state.networkCapabilities != null && state.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) { NetworkSpecifier spec = state.networkCapabilities.getNetworkSpecifier(); - if (spec instanceof StringNetworkSpecifier) { - try { - subId = Integer.parseInt(((StringNetworkSpecifier) spec).specifier); - } catch (NumberFormatException e) { - } + if (spec instanceof TelephonyNetworkSpecifier) { + subId = ((TelephonyNetworkSpecifier) spec).getSubscriptionId(); } } return subId; diff --git a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java index da2ca9b0cfde..eaf120ee2755 100644 --- a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java +++ b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java @@ -42,9 +42,10 @@ public class NotificationChannelExtractor implements NotificationSignalExtractor return null; } - record.updateNotificationChannel(mConfig.getNotificationChannel(record.sbn.getPackageName(), + record.updateNotificationChannel(mConfig.getConversationNotificationChannel( + record.sbn.getPackageName(), record.sbn.getUid(), record.getChannel().getId(), - record.getNotification().getShortcutId(), false)); + record.getNotification().getShortcutId(), true, false)); return null; } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 0e08033e0f68..1d493647304f 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -472,7 +472,8 @@ public class NotificationManagerService extends SystemService { private static final String LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_VALUE = "value"; private RankingHelper mRankingHelper; - private PreferencesHelper mPreferencesHelper; + @VisibleForTesting + PreferencesHelper mPreferencesHelper; private final UserProfiles mUserProfiles = new UserProfiles(); private NotificationListeners mListeners; @@ -3054,23 +3055,24 @@ public class NotificationManagerService extends SystemService { public NotificationChannel getNotificationChannel(String callingPkg, int userId, String targetPkg, String channelId) { return getConversationNotificationChannel( - callingPkg, userId, targetPkg, channelId, null); + callingPkg, userId, targetPkg, channelId, true, null); } @Override public NotificationChannel getConversationNotificationChannel(String callingPkg, int userId, - String targetPkg, String channelId, String conversationId) { + String targetPkg, String channelId, boolean returnParentIfNoConversationChannel, + String conversationId) { if (canNotifyAsPackage(callingPkg, targetPkg, userId) - || isCallingUidSystem()) { + || isCallerIsSystemOrSystemUi()) { int targetUid = -1; try { targetUid = mPackageManagerClient.getPackageUidAsUser(targetPkg, userId); } catch (NameNotFoundException e) { /* ignore */ } - return mPreferencesHelper.getNotificationChannel( + return mPreferencesHelper.getConversationNotificationChannel( targetPkg, targetUid, channelId, conversationId, - false /* includeDeleted */); + returnParentIfNoConversationChannel, false /* includeDeleted */); } throw new SecurityException("Pkg " + callingPkg + " cannot read channels for " + targetPkg + " in " + userId); @@ -5255,9 +5257,15 @@ public class NotificationManagerService extends SystemService { if (mIsTelevision && (new Notification.TvExtender(notification)).getChannelId() != null) { channelId = (new Notification.TvExtender(notification)).getChannelId(); } - final NotificationChannel channel = mPreferencesHelper.getNotificationChannel(pkg, - notificationUid, channelId, notification.getShortcutId(), - false /* includeDeleted */); + // TODO: flag this behavior + String shortcutId = notification.getShortcutId(); + if (shortcutId == null + && notification.getNotificationStyle() == Notification.MessagingStyle.class) { + shortcutId = id + tag + NotificationChannel.PLACEHOLDER_CONVERSATION_ID; + } + final NotificationChannel channel = mPreferencesHelper.getConversationNotificationChannel( + pkg, notificationUid, channelId, shortcutId, + true /* parent ok */, false /* includeDeleted */); if (channel == null) { final String noChannelStr = "No Channel found for " + "pkg=" + pkg @@ -7893,6 +7901,14 @@ public class NotificationManagerService extends SystemService { return isUidSystemOrPhone(Binder.getCallingUid()); } + private boolean isCallerIsSystemOrSystemUi() { + if (isCallerSystemOrPhone()) { + return true; + } + return getContext().checkCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE) + == PERMISSION_GRANTED; + } + private void checkCallerIsSystemOrShell() { int callingUid = Binder.getCallingUid(); if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) { diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 92fcb7f99f9c..185d75c754a2 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -858,12 +858,13 @@ public class PreferencesHelper implements RankingConfig { public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted) { Objects.requireNonNull(pkg); - return getNotificationChannel(pkg, uid, channelId, null, includeDeleted); + return getConversationNotificationChannel(pkg, uid, channelId, null, true, includeDeleted); } @Override - public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, - String conversationId, boolean includeDeleted) { + public NotificationChannel getConversationNotificationChannel(String pkg, int uid, + String channelId, String conversationId, boolean returnParentIfNoConversationChannel, + boolean includeDeleted) { Preconditions.checkNotNull(pkg); synchronized (mPackagePreferences) { PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid); @@ -873,16 +874,19 @@ public class PreferencesHelper implements RankingConfig { if (channelId == null) { channelId = NotificationChannel.DEFAULT_CHANNEL_ID; } - if (conversationId == null) { + NotificationChannel channel = null; + if (conversationId != null) { + // look for an automatically created conversation specific channel + channel = findConversationChannel(r, channelId, conversationId, includeDeleted); + } + if (channel == null && returnParentIfNoConversationChannel) { + // look for it just based on its id final NotificationChannel nc = r.channels.get(channelId); if (nc != null && (includeDeleted || !nc.isDeleted())) { return nc; } - } else { - // look for an automatically created conversation specific channel - return findConversationChannel(r, channelId, conversationId, includeDeleted); } - return null; + return channel; } } diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java index 4b044c13ecc6..7e98be7fe065 100644 --- a/services/core/java/com/android/server/notification/RankingConfig.java +++ b/services/core/java/com/android/server/notification/RankingConfig.java @@ -45,8 +45,9 @@ public interface RankingConfig { boolean fromUser); NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted); - NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, - String conversationId, boolean includeDeleted); + NotificationChannel getConversationNotificationChannel(String pkg, int uid, String channelId, + String conversationId, boolean returnParentIfNoConversationChannel, + boolean includeDeleted); void deleteNotificationChannel(String pkg, int uid, String channelId); void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId); void permanentlyDeleteNotificationChannels(String pkg, int uid); diff --git a/services/core/java/com/android/server/people/PeopleServiceInternal.java b/services/core/java/com/android/server/people/PeopleServiceInternal.java index 31d303621116..c5b868fbe99d 100644 --- a/services/core/java/com/android/server/people/PeopleServiceInternal.java +++ b/services/core/java/com/android/server/people/PeopleServiceInternal.java @@ -16,9 +16,25 @@ package com.android.server.people; +import android.annotation.NonNull; import android.service.appprediction.IPredictionService; /** * @hide Only for use within the system server. */ -public abstract class PeopleServiceInternal extends IPredictionService.Stub {} +public abstract class PeopleServiceInternal extends IPredictionService.Stub { + + /** + * The number conversation infos will be dynamic, based on the currently installed apps on the + * device. All of which should be combined into a single blob to be backed up. + */ + public abstract byte[] backupConversationInfos(@NonNull int userId); + + /** + * Multiple conversation infos may exist in the restore payload, child classes are required to + * manage the restoration based on how individual conversation infos were originally combined + * during backup. + */ + public abstract void restoreConversationInfos(@NonNull int userId, @NonNull String key, + @NonNull byte[] payload); +} diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java index c4bcf809a67d..3ed353483068 100644 --- a/services/core/java/com/android/server/pm/AppsFilter.java +++ b/services/core/java/com/android/server/pm/AppsFilter.java @@ -53,6 +53,7 @@ import java.io.PrintWriter; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.StringTokenizer; /** * The entity responsible for filtering visibility between apps based on declarations in their @@ -219,10 +220,15 @@ public class AppsFilter { continue; } final Uri data = intent.getData(); - if ("content".equalsIgnoreCase(intent.getScheme()) - && data != null - && Objects.equals(provider.getAuthority(), data.getAuthority())) { - return true; + if (!"content".equalsIgnoreCase(intent.getScheme()) || data == null + || provider.getAuthority() == null) { + continue; + } + StringTokenizer authorities = new StringTokenizer(provider.getAuthority(), ";", false); + while (authorities.hasMoreElements()) { + if (Objects.equals(authorities.nextElement(), data.getAuthority())) { + return true; + } } } for (int s = ArrayUtils.size(potentialTarget.getServices()) - 1; s >= 0; s--) { @@ -449,6 +455,7 @@ public class AppsFilter { } final PackageSetting callingPkgSetting; final ArraySet<PackageSetting> callingSharedPkgSettings; + Trace.beginSection("callingSetting instanceof"); if (callingSetting instanceof PackageSetting) { callingPkgSetting = (PackageSetting) callingSetting; callingSharedPkgSettings = null; @@ -456,6 +463,7 @@ public class AppsFilter { callingPkgSetting = null; callingSharedPkgSettings = ((SharedUserSetting) callingSetting).packages; } + Trace.endSection(); if (callingPkgSetting != null) { if (!mFeatureConfig.packageIsEnabled(callingPkgSetting.pkg)) { @@ -485,6 +493,7 @@ public class AppsFilter { return true; } final String targetName = targetPkg.getPackageName(); + Trace.beginSection("getAppId"); final int callingAppId; if (callingPkgSetting != null) { callingAppId = callingPkgSetting.appId; @@ -492,6 +501,7 @@ public class AppsFilter { callingAppId = callingSharedPkgSettings.valueAt(0).appId; // all should be the same } final int targetAppId = targetPkgSetting.appId; + Trace.endSection(); if (callingAppId == targetAppId) { if (DEBUG_LOGGING) { log(callingSetting, targetPkgSetting, "same app id"); @@ -499,38 +509,64 @@ public class AppsFilter { return false; } - if (callingSetting.getPermissionsState().hasPermission( - Manifest.permission.QUERY_ALL_PACKAGES, UserHandle.getUserId(callingUid))) { - if (DEBUG_LOGGING) { - log(callingSetting, targetPkgSetting, "has query-all permission"); + try { + Trace.beginSection("hasPermission"); + if (callingSetting.getPermissionsState().hasPermission( + Manifest.permission.QUERY_ALL_PACKAGES, UserHandle.getUserId(callingUid))) { + if (DEBUG_LOGGING) { + log(callingSetting, targetPkgSetting, "has query-all permission"); + } + return false; } - return false; + } finally { + Trace.endSection(); } - if (mForceQueryable.contains(targetAppId)) { - if (DEBUG_LOGGING) { - log(callingSetting, targetPkgSetting, "force queryable"); + try { + Trace.beginSection("mForceQueryable"); + if (mForceQueryable.contains(targetAppId)) { + if (DEBUG_LOGGING) { + log(callingSetting, targetPkgSetting, "force queryable"); + } + return false; } - return false; + } finally { + Trace.endSection(); } - if (mQueriesViaPackage.contains(callingAppId, targetAppId)) { - // the calling package has explicitly declared the target package; allow - if (DEBUG_LOGGING) { - log(callingSetting, targetPkgSetting, "queries package"); + try { + Trace.beginSection("mQueriesViaPackage"); + if (mQueriesViaPackage.contains(callingAppId, targetAppId)) { + // the calling package has explicitly declared the target package; allow + if (DEBUG_LOGGING) { + log(callingSetting, targetPkgSetting, "queries package"); + } + return false; } - return false; - } else if (mQueriesViaIntent.contains(callingAppId, targetAppId)) { - if (DEBUG_LOGGING) { - log(callingSetting, targetPkgSetting, "queries intent"); + } finally { + Trace.endSection(); + } + try { + Trace.beginSection("mQueriesViaIntent"); + if (mQueriesViaIntent.contains(callingAppId, targetAppId)) { + if (DEBUG_LOGGING) { + log(callingSetting, targetPkgSetting, "queries intent"); + } + return false; } - return false; + } finally { + Trace.endSection(); } - final int targetUid = UserHandle.getUid(userId, targetAppId); - if (mImplicitlyQueryable.contains(callingUid, targetUid)) { - if (DEBUG_LOGGING) { - log(callingSetting, targetPkgSetting, "implicitly queryable for user"); + try { + Trace.beginSection("mImplicitlyQueryable"); + final int targetUid = UserHandle.getUid(userId, targetAppId); + if (mImplicitlyQueryable.contains(callingUid, targetUid)) { + if (DEBUG_LOGGING) { + log(callingSetting, targetPkgSetting, "implicitly queryable for user"); + } + return false; } - return false; + } finally { + Trace.endSection(); } if (callingPkgSetting != null) { if (callingPkgInstruments(callingPkgSetting, targetPkgSetting, targetName)) { @@ -576,17 +612,22 @@ public class AppsFilter { private static boolean callingPkgInstruments(PackageSetting callingPkgSetting, PackageSetting targetPkgSetting, String targetName) { - final List<ComponentParseUtils.ParsedInstrumentation> inst = - callingPkgSetting.pkg.getInstrumentations(); - for (int i = ArrayUtils.size(inst) - 1; i >= 0; i--) { - if (Objects.equals(inst.get(i).getTargetPackage(), targetName)) { - if (DEBUG_LOGGING) { - log(callingPkgSetting, targetPkgSetting, "instrumentation"); + try { + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "callingPkgInstruments"); + final List<ComponentParseUtils.ParsedInstrumentation> inst = + callingPkgSetting.pkg.getInstrumentations(); + for (int i = ArrayUtils.size(inst) - 1; i >= 0; i--) { + if (Objects.equals(inst.get(i).getTargetPackage(), targetName)) { + if (DEBUG_LOGGING) { + log(callingPkgSetting, targetPkgSetting, "instrumentation"); + } + return true; } - return true; } + return false; + } finally { + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } - return false; } private static void log(SettingBase callingPkgSetting, PackageSetting targetPkgSetting, @@ -597,7 +638,7 @@ public class AppsFilter { private static void log(SettingBase callingPkgSetting, PackageSetting targetPkgSetting, String description, Throwable throwable) { Slog.wtf(TAG, - "interaction: " + callingPkgSetting.toString() + "interaction: " + callingPkgSetting + " -> " + targetPkgSetting.name + " " + description, throwable); } diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java index ffcd6cf8603c..bcfe5773cfa4 100644 --- a/services/core/java/com/android/server/pm/InstantAppRegistry.java +++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java @@ -338,9 +338,8 @@ class InstantAppRegistry { } @GuardedBy("mService.mLock") - public void onPackageUninstalledLPw(@NonNull AndroidPackage pkg, + public void onPackageUninstalledLPw(@NonNull AndroidPackage pkg, @Nullable PackageSetting ps, @NonNull int[] userIds) { - PackageSetting ps = mService.getPackageSetting(pkg.getPackageName()); if (ps == null) { return; } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index dad32cd6c83e..d0f91c206a2c 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -190,6 +190,7 @@ import android.content.pm.SELinuxUtil; import android.content.pm.ServiceInfo; import android.content.pm.SharedLibraryInfo; import android.content.pm.Signature; +import android.content.pm.SigningInfo; import android.content.pm.SuspendDialogInfo; import android.content.pm.UserInfo; import android.content.pm.VerifierDeviceIdentity; @@ -1533,7 +1534,7 @@ public class PackageManagerService extends IPackageManager.Stub final @Nullable String mAppPredictionServicePackage; final @Nullable String mIncidentReportApproverPackage; final @Nullable String[] mTelephonyPackages; - final @NonNull String mServicesSystemSharedLibraryPackageName; + final @NonNull String mServicesExtensionPackageName; final @NonNull String mSharedSystemSharedLibraryPackageName; private final PackageUsage mPackageUsage = new PackageUsage(); @@ -3302,9 +3303,7 @@ public class PackageManagerService extends IPackageManager.Stub } else { mIntentFilterVerifier = null; } - mServicesSystemSharedLibraryPackageName = getRequiredSharedLibraryLPr( - PackageManager.SYSTEM_SHARED_LIBRARY_SERVICES, - SharedLibraryInfo.VERSION_UNDEFINED); + mServicesExtensionPackageName = getRequiredServicesExtensionPackageLPr(); mSharedSystemSharedLibraryPackageName = getRequiredSharedLibraryLPr( PackageManager.SYSTEM_SHARED_LIBRARY_SHARED, SharedLibraryInfo.VERSION_UNDEFINED); @@ -3314,7 +3313,7 @@ public class PackageManagerService extends IPackageManager.Stub mRequiredUninstallerPackage = null; mIntentFilterVerifierComponent = null; mIntentFilterVerifier = null; - mServicesSystemSharedLibraryPackageName = null; + mServicesExtensionPackageName = null; mSharedSystemSharedLibraryPackageName = null; } // PermissionController hosts default permission granting and role management, so it's a @@ -3744,6 +3743,19 @@ public class PackageManagerService extends IPackageManager.Stub } } + @NonNull + private String getRequiredServicesExtensionPackageLPr() { + String servicesExtensionPackage = + ensureSystemPackageName( + mContext.getString(R.string.config_servicesExtensionPackage)); + if (TextUtils.isEmpty(servicesExtensionPackage)) { + throw new RuntimeException( + "Required services extension package is missing, check " + + "config_servicesExtensionPackage."); + } + return servicesExtensionPackage; + } + private @NonNull String getRequiredInstallerLPr() { final Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE); intent.addCategory(Intent.CATEGORY_DEFAULT); @@ -5466,7 +5478,7 @@ public class PackageManagerService extends IPackageManager.Stub public @NonNull String getServicesSystemSharedLibraryPackageName() { // allow instant applications synchronized (mLock) { - return mServicesSystemSharedLibraryPackageName; + return mServicesExtensionPackageName; } } @@ -17502,7 +17514,8 @@ public class PackageManagerService extends IPackageManager.Stub synchronized (mLock) { if (res) { if (pkg != null) { - mInstantAppRegistry.onPackageUninstalledLPw(pkg, info.removedUsers); + mInstantAppRegistry.onPackageUninstalledLPw(pkg, uninstalledPs, + info.removedUsers); } updateSequenceNumberLP(uninstalledPs, info.removedUsers); updateInstantAppInstallerLocked(packageName); @@ -20020,8 +20033,9 @@ public class PackageManagerService extends IPackageManager.Stub String initiatingPackageName; String originatingPackageName; + final InstallSource installSource; synchronized (mLock) { - final InstallSource installSource = getInstallSourceLocked(packageName, callingUid); + installSource = getInstallSourceLocked(packageName, callingUid); if (installSource == null) { return null; } @@ -20035,9 +20049,16 @@ public class PackageManagerService extends IPackageManager.Stub } if (installSource.isInitiatingPackageUninstalled) { - // TODO(b/146555198) Allow the app itself to see the info - // (at least for non-instant apps) - initiatingPackageName = null; + // We can't check visibility in the usual way, since the initiating package is no + // longer present. So we apply simpler rules to whether to expose the info: + // 1. Instant apps can't see it. + // 2. Otherwise only the installed app itself can see it. + final boolean isInstantApp = getInstantAppPackageName(callingUid) != null; + if (!isInstantApp && isCallerSameApp(packageName, callingUid)) { + initiatingPackageName = installSource.initiatingPackageName; + } else { + initiatingPackageName = null; + } } else { // All installSource strings are interned, so == is ok here if (installSource.initiatingPackageName == installSource.installerPackageName) { @@ -20062,13 +20083,27 @@ public class PackageManagerService extends IPackageManager.Stub } } + // Remaining work can safely be done outside the lock. (Note that installSource is + // immutable so it's ok to carry on reading from it.) + if (originatingPackageName != null && mContext.checkCallingOrSelfPermission( Manifest.permission.INSTALL_PACKAGES) != PackageManager.PERMISSION_GRANTED) { originatingPackageName = null; } - return new InstallSourceInfo(initiatingPackageName, originatingPackageName, - installerPackageName); + // If you can see the initiatingPackageName, and we have valid signing info for it, + // then we let you see that too. + final SigningInfo initiatingPackageSigningInfo; + final PackageSignatures signatures = installSource.initiatingPackageSignatures; + if (initiatingPackageName != null && signatures != null + && signatures.mSigningDetails != SigningDetails.UNKNOWN) { + initiatingPackageSigningInfo = new SigningInfo(signatures.mSigningDetails); + } else { + initiatingPackageSigningInfo = null; + } + + return new InstallSourceInfo(initiatingPackageName, initiatingPackageSigningInfo, + originatingPackageName, installerPackageName); } @GuardedBy("mLock") @@ -22266,6 +22301,13 @@ public class PackageManagerService extends IPackageManager.Stub mPermissionManager.onNewUserCreated(userId); } + boolean readPermissionStateForUser(@UserIdInt int userId) { + synchronized (mPackages) { + mSettings.readPermissionStateForUserSyncLPr(userId); + return mSettings.areDefaultRuntimePermissionsGrantedLPr(userId); + } + } + @Override public VerifierDeviceIdentity getVerifierDeviceIdentity() throws RemoteException { mContext.enforceCallingOrSelfPermission( diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 3e665c324f0d..4f18cb43cc94 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -3125,6 +3125,10 @@ public final class Settings { return true; } + void readPermissionStateForUserSyncLPr(@UserIdInt int userId) { + mRuntimePermissionsPersistence.readStateForUserSyncLPr(userId); + } + void applyDefaultPreferredAppsLPw(int userId) { // First pull data from any pre-installed apps. final PackageManagerInternal pmInternal = diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 2265d010216e..7888d1f9612f 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -49,6 +49,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; +import android.os.ParcelableException; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; @@ -570,18 +571,17 @@ public class StagingManager { } else { params.installFlags |= PackageManager.INSTALL_DISABLE_VERIFICATION; } - int apkSessionId = mPi.createSession( - params, originalSession.getInstallerPackageName(), - 0 /* UserHandle.SYSTEM */); - PackageInstallerSession apkSession = mPi.getSession(apkSessionId); - try { + int apkSessionId = mPi.createSession( + params, originalSession.getInstallerPackageName(), + 0 /* UserHandle.SYSTEM */); + PackageInstallerSession apkSession = mPi.getSession(apkSessionId); apkSession.open(); for (String apkFilePath : apkFilePaths) { File apkFile = new File(apkFilePath); ParcelFileDescriptor pfd = ParcelFileDescriptor.open(apkFile, ParcelFileDescriptor.MODE_READ_ONLY); - long sizeBytes = pfd.getStatSize(); + long sizeBytes = (pfd == null) ? -1 : pfd.getStatSize(); if (sizeBytes < 0) { Slog.e(TAG, "Unable to get size of: " + apkFilePath); throw new PackageManagerException(errorCode, @@ -589,11 +589,11 @@ public class StagingManager { } apkSession.write(apkFile.getName(), 0, sizeBytes, pfd); } - } catch (IOException e) { + return apkSession; + } catch (IOException | ParcelableException e) { Slog.e(TAG, "Failure to install APK staged session " + originalSession.sessionId, e); - throw new PackageManagerException(errorCode, "Failed to write APK session", e); + throw new PackageManagerException(errorCode, "Failed to create/write APK session", e); } - return apkSession; } /** diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index e5d5b57113c0..66a2b013e299 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -492,6 +492,10 @@ public class UserManagerService extends IUserManager.Stub { public void onBootPhase(int phase) { if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { mUms.cleanupPartialUsers(); + + if (mUms.mPm.isDeviceUpgrading()) { + mUms.cleanupPreCreatedUsers(); + } } } @@ -617,6 +621,33 @@ public class UserManagerService extends IUserManager.Stub { } } + /** + * Removes any pre-created users from the system. Should be invoked after OTAs, to ensure + * pre-created users are not stale. New pre-created pool can be re-created after the update. + */ + void cleanupPreCreatedUsers() { + final ArrayList<UserInfo> preCreatedUsers; + synchronized (mUsersLock) { + final int userSize = mUsers.size(); + preCreatedUsers = new ArrayList<>(userSize); + for (int i = 0; i < userSize; i++) { + UserInfo ui = mUsers.valueAt(i).info; + if (ui.preCreated) { + preCreatedUsers.add(ui); + addRemovingUserIdLocked(ui.id); + ui.flags |= UserInfo.FLAG_DISABLED; + ui.partial = true; + } + } + } + final int preCreatedSize = preCreatedUsers.size(); + for (int i = 0; i < preCreatedSize; i++) { + UserInfo ui = preCreatedUsers.get(i); + Slog.i(LOG_TAG, "Removing pre-created user " + ui.id); + removeUserState(ui.id); + } + } + @Override public String getUserAccount(@UserIdInt int userId) { checkManageUserAndAcrossUsersFullPermission("get user account"); @@ -3078,7 +3109,6 @@ public class UserManagerService extends IUserManager.Stub { @NonNull String userType, @UserInfoFlag int flags, @UserIdInt int parentId, boolean preCreate, @Nullable String[] disallowedPackages, @NonNull TimingsTraceAndSlog t) { - final UserTypeDetails userTypeDetails = mUserTypes.get(userType); if (userTypeDetails == null) { Slog.e(LOG_TAG, "Cannot create user of invalid user type: " + userType); @@ -3254,9 +3284,9 @@ public class UserManagerService extends IUserManager.Stub { mBaseUserRestrictions.append(userId, restrictions); } - t.traceBegin("PM.onNewUserCreated"); + t.traceBegin("PM.onNewUserCreated-" + userId); mPm.onNewUserCreated(userId); - + t.traceEnd(); if (preCreate) { // Must start user (which will be stopped right away, through // UserController.finishUserUnlockedCompleted) so services can properly @@ -3323,11 +3353,16 @@ public class UserManagerService extends IUserManager.Stub { preCreatedUser.preCreated = false; preCreatedUser.creationTime = getCreationTime(); - dispatchUserAddedIntent(preCreatedUser); synchronized (mPackagesLock) { writeUserLP(preCreatedUserData); writeUserListLP(); } + updateUserIds(); + if (!mPm.readPermissionStateForUser(preCreatedUser.id)) { + // Could not read the existing permissions, re-grant them. + mPm.onNewUserCreated(preCreatedUser.id); + } + dispatchUserAddedIntent(preCreatedUser); return preCreatedUser; } @@ -3360,6 +3395,9 @@ public class UserManagerService extends IUserManager.Stub { private void dispatchUserAddedIntent(@NonNull UserInfo userInfo) { Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED); addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userInfo.id); + // Also, add the UserHandle for mainline modules which can't use the @hide + // EXTRA_USER_HANDLE. + addedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userInfo.id)); mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL, android.Manifest.permission.MANAGE_USERS); MetricsLogger.count(mContext, userInfo.isGuest() ? TRON_GUEST_CREATED @@ -3643,9 +3681,12 @@ public class UserManagerService extends IUserManager.Stub { // wiping the user's system directory and removing from the user list long ident = Binder.clearCallingIdentity(); try { - Intent addedIntent = new Intent(Intent.ACTION_USER_REMOVED); - addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId); - mContext.sendOrderedBroadcastAsUser(addedIntent, UserHandle.ALL, + Intent removedIntent = new Intent(Intent.ACTION_USER_REMOVED); + removedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId); + // Also, add the UserHandle for mainline modules which can't use the @hide + // EXTRA_USER_HANDLE. + removedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId)); + mContext.sendOrderedBroadcastAsUser(removedIntent, UserHandle.ALL, android.Manifest.permission.MANAGE_USERS, new BroadcastReceiver() { @@ -4027,14 +4068,16 @@ public class UserManagerService extends IUserManager.Stub { synchronized (mUsersLock) { final int userSize = mUsers.size(); for (int i = 0; i < userSize; i++) { - if (!mUsers.valueAt(i).info.partial) { + UserInfo userInfo = mUsers.valueAt(i).info; + if (!userInfo.partial && !userInfo.preCreated) { num++; } } final int[] newUsers = new int[num]; int n = 0; for (int i = 0; i < userSize; i++) { - if (!mUsers.valueAt(i).info.partial) { + UserInfo userInfo = mUsers.valueAt(i).info; + if (!userInfo.partial && !userInfo.preCreated) { newUsers[n++] = mUsers.keyAt(i); } } @@ -4095,7 +4138,10 @@ public class UserManagerService extends IUserManager.Stub { * recycled. */ void reconcileUsers(String volumeUuid) { - mUserDataPreparer.reconcileUsers(volumeUuid, getUsers(true /* excludeDying */)); + mUserDataPreparer.reconcileUsers(volumeUuid, getUsers( + /* excludePartial= */ true, + /* excludeDying= */ true, + /* excludePreCreated= */ false)); } /** diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index 815f7b4357bf..89030ed7c075 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -199,13 +199,31 @@ public class UserRestrictionsUtils { ); /** - * Special user restrictions that are applied globally when set by the profile owner of a - * managed profile that was created during the device provisioning flow. + * Special user restrictions that profile owner of an organization-owned managed profile can + * set on the parent profile instance to apply them globally. */ private static final Set<String> PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS = Sets.newArraySet( UserManager.DISALLOW_CONFIG_DATE_TIME, - UserManager.DISALLOW_CAMERA + UserManager.DISALLOW_CAMERA, + UserManager.DISALLOW_ADD_USER, + UserManager.DISALLOW_BLUETOOTH, + UserManager.DISALLOW_BLUETOOTH_SHARING, + UserManager.DISALLOW_CONFIG_BLUETOOTH, + UserManager.DISALLOW_CONFIG_CELL_BROADCASTS, + UserManager.DISALLOW_CONFIG_LOCATION, + UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, + UserManager.DISALLOW_CONFIG_PRIVATE_DNS, + UserManager.DISALLOW_CONFIG_TETHERING, + UserManager.DISALLOW_CONFIG_WIFI, + UserManager.DISALLOW_CONTENT_CAPTURE, + UserManager.DISALLOW_CONTENT_SUGGESTIONS, + UserManager.DISALLOW_DATA_ROAMING, + UserManager.DISALLOW_DEBUGGING_FEATURES, + UserManager.DISALLOW_SAFE_BOOT, + UserManager.DISALLOW_SHARE_LOCATION, + UserManager.DISALLOW_SMS, + UserManager.DISALLOW_USB_FILE_TRANSFER ); /** diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java index 29183bb78f07..df24c0130061 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -222,6 +222,7 @@ public class DexManager { // If the dex file is the primary apk (or a split) and not isUsedByOtherApps // do not record it. This case does not bring any new usable information // and can be safely skipped. + dexPathIndex++; continue; } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index d468cd981b52..0411e29f68a1 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -55,6 +55,8 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ApplicationPackageManager; import android.app.IActivityManager; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -107,6 +109,7 @@ import android.util.SparseArray; import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.compat.IPlatformCompat; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.os.RoSystemProperties; @@ -224,6 +227,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { private final Handler mHandler; private final Context mContext; private final MetricsLogger mMetricsLogger = new MetricsLogger(); + private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface( + ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); /** Internal storage for permissions and related settings */ @GuardedBy("mLock") @@ -1824,6 +1829,14 @@ public class PermissionManagerService extends IPermissionManager.Stub { return true; } + /** + * This change makes it so that apps are told to show rationale for asking for background + * location access every time they request. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) + private static final long BACKGROUND_RATIONALE_CHANGE_ID = 147316723L; + @Override public boolean shouldShowRequestPermissionRationale(String permName, String packageName, int userId) { @@ -1862,6 +1875,16 @@ public class PermissionManagerService extends IPermissionManager.Stub { return false; } + try { + if (permName.equals(Manifest.permission.ACCESS_BACKGROUND_LOCATION) + && mPlatformCompat.isChangeEnabledByPackageName(BACKGROUND_RATIONALE_CHANGE_ID, + packageName, userId)) { + return true; + } + } catch (RemoteException e) { + Log.e(TAG, "Unable to check if compatibility change is enabled.", e); + } + return (flags & PackageManager.FLAG_PERMISSION_USER_SET) != 0; } diff --git a/services/core/java/com/android/server/policy/LegacyGlobalActions.java b/services/core/java/com/android/server/policy/LegacyGlobalActions.java index 52714933c8e2..6daf5162ebad 100644 --- a/services/core/java/com/android/server/policy/LegacyGlobalActions.java +++ b/services/core/java/com/android/server/policy/LegacyGlobalActions.java @@ -743,8 +743,8 @@ class LegacyGlobalActions implements DialogInterface.OnDismissListener, DialogIn } else if (TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) { // Airplane mode can be changed after ECM exits if airplane toggle button // is pressed during ECM mode - if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) && - mIsWaitingForEcmExit) { + if (!(intent.getBooleanExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false)) + && mIsWaitingForEcmExit) { mIsWaitingForEcmExit = false; changeAirplaneModeSystemSetting(true); } diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java index a62bb74730f8..4b3746b5141e 100644 --- a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java +++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java @@ -45,7 +45,6 @@ import com.android.server.EventLogTags; import com.android.server.power.BatterySaverStateMachineProto; import java.io.PrintWriter; -import java.text.NumberFormat; /** * Decides when to enable / disable battery saver. @@ -796,8 +795,7 @@ public class BatterySaverStateMachine { manager.notifyAsUser(TAG, DYNAMIC_MODE_NOTIFICATION_ID, buildNotification(DYNAMIC_MODE_NOTIF_CHANNEL_ID, - mContext.getResources().getString( - R.string.dynamic_mode_notification_title), + R.string.dynamic_mode_notification_title, R.string.dynamic_mode_notification_summary, Intent.ACTION_POWER_USAGE_SUMMARY), UserHandle.ALL); @@ -813,13 +811,10 @@ public class BatterySaverStateMachine { ensureNotificationChannelExists(manager, BATTERY_SAVER_NOTIF_CHANNEL_ID, R.string.battery_saver_notification_channel_name); - final String percentage = NumberFormat.getPercentInstance() - .format((double) mBatteryLevel / 100.0); manager.notifyAsUser(TAG, STICKY_AUTO_DISABLED_NOTIFICATION_ID, buildNotification(BATTERY_SAVER_NOTIF_CHANNEL_ID, - mContext.getResources().getString( - R.string.battery_saver_charged_notification_title, percentage), - R.string.battery_saver_off_notification_summary, + R.string.battery_saver_off_notification_title, + R.string.battery_saver_charged_notification_summary, Settings.ACTION_BATTERY_SAVER_SETTINGS), UserHandle.ALL); }); @@ -834,13 +829,14 @@ public class BatterySaverStateMachine { manager.createNotificationChannel(channel); } - private Notification buildNotification(@NonNull String channelId, @NonNull String title, + private Notification buildNotification(@NonNull String channelId, @StringRes int titleId, @StringRes int summaryId, @NonNull String intentAction) { Resources res = mContext.getResources(); Intent intent = new Intent(intentAction); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); PendingIntent batterySaverIntent = PendingIntent.getActivity( mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_UPDATE_CURRENT); + final String title = res.getString(titleId); final String summary = res.getString(summaryId); return new Notification.Builder(mContext, channelId) diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java index 9f592b85a5e6..5c0dd9a44dc4 100644 --- a/services/core/java/com/android/server/rollback/Rollback.java +++ b/services/core/java/com/android/server/rollback/Rollback.java @@ -172,6 +172,13 @@ class Rollback { @Nullable public final String mInstallerPackageName; /** + * This array holds all of the rollback tokens associated with package sessions included in + * this rollback. + */ + @GuardedBy("mLock") + private final IntArray mTokens = new IntArray(); + + /** * Constructs a new, empty Rollback instance. * * @param rollbackId the id of the rollback. @@ -353,7 +360,7 @@ class Rollback { */ boolean enableForPackageInApex(String packageName, long installedVersion, int rollbackDataPolicy) { - // TODO(b/142712057): Extract the new version number of apk-in-apex + // TODO(b/147666157): Extract the new version number of apk-in-apex // The new version for the apk-in-apex is set to 0 for now. If the package is then further // updated via non-staged install flow, then RollbackManagerServiceImpl#onPackageReplaced() // will be called and this rollback will be deleted. Other ways of package update have not @@ -766,6 +773,26 @@ class Rollback { } } + /** + * Adds a rollback token to be associated with this rollback. This may be used to + * identify which rollback should be removed in case {@link PackageManager} sends an + * {@link Intent#ACTION_CANCEL_ENABLE_ROLLBACK} intent. + */ + void addToken(int token) { + synchronized (mLock) { + mTokens.add(token); + } + } + + /** + * Returns true if this rollback is associated with the provided {@code token}. + */ + boolean hasToken(int token) { + synchronized (mLock) { + return mTokens.indexOf(token) != -1; + } + } + static String rollbackStateToString(@RollbackState int state) { switch (state) { case Rollback.ROLLBACK_STATE_ENABLING: return "enabling"; diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index 8f8a5c4b14e9..de48939825e4 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -43,6 +43,7 @@ import android.content.rollback.RollbackManager; import android.os.Binder; import android.os.Environment; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.HandlerThread; import android.os.Process; import android.os.SystemClock; @@ -78,6 +79,7 @@ import java.util.List; import java.util.Random; import java.util.Set; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; @@ -134,6 +136,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { private final Context mContext; private final HandlerThread mHandlerThread; + private final Executor mExecutor; private final Installer mInstaller; private final RollbackPackageHealthObserver mPackageHealthObserver; private final AppDataRollbackHelper mAppDataRollbackHelper; @@ -173,6 +176,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { mHandlerThread = new HandlerThread("RollbackManagerServiceHandler"); mHandlerThread.start(); Watchdog.getInstance().addThread(getHandler(), HANDLER_THREAD_TIMEOUT_DURATION_MILLIS); + mExecutor = new HandlerExecutor(getHandler()); for (UserInfo userInfo : UserManager.get(mContext).getUsers(true)) { registerUserCallbacks(userInfo.getUserHandle()); @@ -235,12 +239,17 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { Slog.v(TAG, "broadcast=ACTION_CANCEL_ENABLE_ROLLBACK token=" + token); } synchronized (mLock) { - for (NewRollback rollback : mNewRollbacks) { - if (rollback.hasToken(token)) { - rollback.setCancelled(); - return; + NewRollback found = null; + for (NewRollback newRollback : mNewRollbacks) { + if (newRollback.rollback.hasToken(token)) { + found = newRollback; + break; } } + if (found != null) { + mNewRollbacks.remove(found); + found.rollback.delete(mAppDataRollbackHelper); + } } } } @@ -404,7 +413,6 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { CountDownLatch latch = new CountDownLatch(1); getHandler().post(() -> { - updateRollbackLifetimeDurationInMillis(); synchronized (mLock) { mRollbacks.clear(); mRollbacks.addAll(mRollbackStore.loadRollbacks()); @@ -433,10 +441,14 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { rollback.delete(mAppDataRollbackHelper); } } - for (NewRollback newRollback : mNewRollbacks) { + Iterator<NewRollback> iter2 = mNewRollbacks.iterator(); + while (iter2.hasNext()) { + NewRollback newRollback = iter2.next(); if (newRollback.rollback.includesPackage(packageName)) { - newRollback.setCancelled(); + iter2.remove(); + newRollback.rollback.delete(mAppDataRollbackHelper); } + } } } @@ -511,11 +523,13 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { @AnyThread void onBootCompleted() { - getHandler().post(() -> updateRollbackLifetimeDurationInMillis()); - // Also posts to handler thread - scheduleExpiration(0); + DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ROLLBACK_BOOT, + mExecutor, properties -> updateRollbackLifetimeDurationInMillis()); getHandler().post(() -> { + updateRollbackLifetimeDurationInMillis(); + runExpiration(); + // Check to see if any rollback-enabled staged sessions or staged // rollback sessions been applied. List<Rollback> enabling = new ArrayList<>(); @@ -798,7 +812,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { mNewRollbacks.add(newRollback); } } - newRollback.addToken(token); + newRollback.rollback.addToken(token); return enableRollbackForPackageSession(newRollback.rollback, packageSession); } @@ -1220,12 +1234,6 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { Slog.v(TAG, "completeEnableRollback id=" + rollback.info.getRollbackId()); } - if (newRollback.isCancelled()) { - Slog.e(TAG, "Rollback has been cancelled by PackageManager"); - rollback.delete(mAppDataRollbackHelper); - return null; - } - // We are checking if number of packages (excluding apk-in-apex) we enabled for rollback is // equal to the number of sessions we are installing, to ensure we didn't skip enabling // of any sessions. If we successfully enable an apex, then we can assume we enabled @@ -1335,22 +1343,12 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { public final Rollback rollback; /** - * This array holds all of the rollback tokens associated with package sessions included in - * this rollback. - */ - @GuardedBy("mNewRollbackLock") - private final IntArray mTokens = new IntArray(); - - /** * Session ids for all packages in the install. For multi-package sessions, this is the list * of child session ids. For normal sessions, this list is a single element with the normal * session id. */ private final int[] mPackageSessionIds; - @GuardedBy("mNewRollbackLock") - private boolean mIsCancelled = false; - /** * The number of sessions in the install which are notified with success by * {@link PackageInstaller.SessionCallback#onFinished(int, boolean)}. @@ -1367,52 +1365,6 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { } /** - * Adds a rollback token to be associated with this NewRollback. This may be used to - * identify which rollback should be cancelled in case {@link PackageManager} sends an - * {@link Intent#ACTION_CANCEL_ENABLE_ROLLBACK} intent. - */ - void addToken(int token) { - synchronized (mNewRollbackLock) { - mTokens.add(token); - } - } - - /** - * Returns true if this NewRollback is associated with the provided {@code token}. - */ - boolean hasToken(int token) { - synchronized (mNewRollbackLock) { - return mTokens.indexOf(token) != -1; - } - } - - /** - * Returns true if this NewRollback has been cancelled. - * - * <p>Rollback could be invalidated and cancelled if RollbackManager receives - * {@link Intent#ACTION_CANCEL_ENABLE_ROLLBACK} from {@link PackageManager}. - * - * <p>The main underlying assumption here is that if enabling the rollback times out, then - * {@link PackageManager} will NOT send - * {@link PackageInstaller.SessionCallback#onFinished(int, boolean)} before it broadcasts - * {@link Intent#ACTION_CANCEL_ENABLE_ROLLBACK}. - */ - boolean isCancelled() { - synchronized (mNewRollbackLock) { - return mIsCancelled; - } - } - - /** - * Sets this NewRollback to be marked as cancelled. - */ - void setCancelled() { - synchronized (mNewRollbackLock) { - mIsCancelled = true; - } - } - - /** * Returns true if this NewRollback contains the provided {@code packageSessionId}. */ boolean containsSessionId(int packageSessionId) { diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java index a0ef8cfec80f..6686de95c05c 100644 --- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java +++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java @@ -179,10 +179,10 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve // Use the version of the metadata package that was installed before // we rolled back for logging purposes. - VersionedPackage oldModuleMetadataPackage = null; + VersionedPackage oldLogPackage = null; for (PackageRollbackInfo packageRollback : rollback.getPackages()) { if (packageRollback.getPackageName().equals(moduleMetadataPackageName)) { - oldModuleMetadataPackage = packageRollback.getVersionRolledBackFrom(); + oldLogPackage = packageRollback.getVersionRolledBackFrom(); break; } } @@ -194,13 +194,13 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve return; } if (sessionInfo.isStagedSessionApplied()) { - logEvent(oldModuleMetadataPackage, + logEvent(oldLogPackage, StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS, WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN, ""); } else if (sessionInfo.isStagedSessionReady()) { // TODO: What do for staged session ready but not applied } else { - logEvent(oldModuleMetadataPackage, + logEvent(oldLogPackage, StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE, WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN, ""); } @@ -213,6 +213,23 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve if (packageRollback.getVersionRolledBackFrom().equals(failedPackage)) { return rollback; } + // TODO(b/147666157): Extract version number of apk-in-apex so that we don't have + // to rely on complicated reasoning as below + + // Due to b/147666157, for apk in apex, we do not know the version we are rolling + // back from. But if a package X is embedded in apex A exclusively (not embedded in + // any other apex), which is not guaranteed, then it is sufficient to check only + // package names here, as the version of failedPackage and the PackageRollbackInfo + // can't be different. If failedPackage has a higher version, then it must have + // been updated somehow. There are two ways: it was updated by an update of apex A + // or updated directly as apk. In both cases, this rollback would have gotten + // expired when onPackageReplaced() was called. Since the rollback exists, it has + // same version as failedPackage. + if (packageRollback.isApkInApex() + && packageRollback.getVersionRolledBackFrom().getPackageName() + .equals(failedPackage.getPackageName())) { + return rollback; + } } } return null; @@ -245,12 +262,12 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve } private BroadcastReceiver listenForStagedSessionReady(RollbackManager rollbackManager, - int rollbackId, @Nullable VersionedPackage moduleMetadataPackage) { + int rollbackId, @Nullable VersionedPackage logPackage) { BroadcastReceiver sessionUpdatedReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { handleStagedSessionChange(rollbackManager, - rollbackId, this /* BroadcastReceiver */, moduleMetadataPackage); + rollbackId, this /* BroadcastReceiver */, logPackage); } }; IntentFilter sessionUpdatedFilter = @@ -260,7 +277,7 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve } private void handleStagedSessionChange(RollbackManager rollbackManager, int rollbackId, - BroadcastReceiver listener, @Nullable VersionedPackage moduleMetadataPackage) { + BroadcastReceiver listener, @Nullable VersionedPackage logPackage) { PackageInstaller packageInstaller = mContext.getPackageManager().getPackageInstaller(); List<RollbackInfo> recentRollbacks = @@ -274,16 +291,19 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve packageInstaller.getSessionInfo(sessionId); if (sessionInfo.isStagedSessionReady() && markStagedSessionHandled(rollbackId)) { mContext.unregisterReceiver(listener); - saveLastStagedRollbackId(rollbackId); - logEvent(moduleMetadataPackage, + if (logPackage != null) { + // We save the rollback id so that after reboot, we can log if rollback was + // successful or not. If logPackage is null, then there is nothing to log. + saveLastStagedRollbackId(rollbackId); + } + logEvent(logPackage, StatsLog .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED, WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN, ""); - mContext.getSystemService(PowerManager.class).reboot("Rollback staged install"); } else if (sessionInfo.isStagedSessionFailed() && markStagedSessionHandled(rollbackId)) { - logEvent(moduleMetadataPackage, + logEvent(logPackage, StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE, WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN, ""); @@ -291,6 +311,11 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve } } } + + // Wait for all pending staged sessions to get handled before rebooting. + if (isPendingStagedSessionsEmpty()) { + mContext.getSystemService(PowerManager.class).reboot("Rollback staged install"); + } } /** @@ -303,6 +328,16 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve } } + /** + * Returns {@code true} if all pending staged rollback sessions were marked as handled, + * {@code false} if there is any left. + */ + private boolean isPendingStagedSessionsEmpty() { + synchronized (mPendingStagedRollbackIds) { + return mPendingStagedRollbackIds.isEmpty(); + } + } + private void saveLastStagedRollbackId(int stagedRollbackId) { try { FileOutputStream fos = new FileOutputStream(mLastStagedRollbackIdFile); @@ -348,12 +383,12 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve } } - private static void logEvent(@Nullable VersionedPackage moduleMetadataPackage, int type, + private static void logEvent(@Nullable VersionedPackage logPackage, int type, int rollbackReason, @NonNull String failingPackageName) { Slog.i(TAG, "Watchdog event occurred of type: " + rollbackTypeToString(type)); - if (moduleMetadataPackage != null) { - StatsLog.logWatchdogRollbackOccurred(type, moduleMetadataPackage.getPackageName(), - moduleMetadataPackage.getVersionCode(), rollbackReason, failingPackageName); + if (logPackage != null) { + StatsLog.logWatchdogRollbackOccurred(type, logPackage.getPackageName(), + logPackage.getVersionCode(), rollbackReason, failingPackageName); } } @@ -414,6 +449,9 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve reasonToLog, failedPackageToLog); } } else { + if (rollback.isStaged()) { + markStagedSessionHandled(rollback.getRollbackId()); + } logEvent(logPackage, StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE, reasonToLog, failedPackageToLog); @@ -431,6 +469,16 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class); List<RollbackInfo> rollbacks = rollbackManager.getAvailableRollbacks(); + // Add all rollback ids to mPendingStagedRollbackIds, so that we do not reboot before all + // pending staged rollbacks are handled. + synchronized (mPendingStagedRollbackIds) { + for (RollbackInfo rollback : rollbacks) { + if (rollback.isStaged()) { + mPendingStagedRollbackIds.add(rollback.getRollbackId()); + } + } + } + for (RollbackInfo rollback : rollbacks) { VersionedPackage sample = rollback.getPackages().get(0).getVersionRolledBackFrom(); rollbackPackage(rollback, sample, PackageWatchdog.FAILURE_REASON_NATIVE_CRASH); diff --git a/services/core/java/com/android/server/soundtrigger_middleware/HalFactory.java b/services/core/java/com/android/server/soundtrigger_middleware/HalFactory.java new file mode 100644 index 000000000000..b19e2ed1a91b --- /dev/null +++ b/services/core/java/com/android/server/soundtrigger_middleware/HalFactory.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.soundtrigger_middleware; + +import android.hardware.soundtrigger.V2_0.ISoundTriggerHw; + +/** + * A factory for creating instances of {@link ISoundTriggerHw}. + * + * @hide + */ +public interface HalFactory { + /** + * @return An instance of {@link ISoundTriggerHw}. + */ + ISoundTriggerHw create(); +} diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java index 9d51b65ea152..9f4b09a62aff 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java @@ -78,18 +78,17 @@ public class SoundTriggerMiddlewareImpl implements ISoundTriggerMiddlewareServic } /** - * Most generic constructor - gets an array of HAL driver instances. + * Constructor - gets an array of HAL driver factories. */ - public SoundTriggerMiddlewareImpl(@NonNull ISoundTriggerHw[] halServices, + public SoundTriggerMiddlewareImpl(@NonNull HalFactory[] halFactories, @NonNull AudioSessionProvider audioSessionProvider) { - List<SoundTriggerModule> modules = new ArrayList<>(halServices.length); + List<SoundTriggerModule> modules = new ArrayList<>(halFactories.length); - for (int i = 0; i < halServices.length; ++i) { - ISoundTriggerHw service = halServices[i]; + for (int i = 0; i < halFactories.length; ++i) { try { - modules.add(new SoundTriggerModule(service, audioSessionProvider)); + modules.add(new SoundTriggerModule(halFactories[i], audioSessionProvider)); } catch (Exception e) { - Log.e(TAG, "Failed to a SoundTriggerModule instance", e); + Log.e(TAG, "Failed to add a SoundTriggerModule instance", e); } } @@ -97,11 +96,11 @@ public class SoundTriggerMiddlewareImpl implements ISoundTriggerMiddlewareServic } /** - * Convenience constructor - gets a single HAL driver instance. + * Convenience constructor - gets a single HAL factory. */ - public SoundTriggerMiddlewareImpl(@NonNull ISoundTriggerHw halService, + public SoundTriggerMiddlewareImpl(@NonNull HalFactory factory, @NonNull AudioSessionProvider audioSessionProvider) { - this(new ISoundTriggerHw[]{halService}, audioSessionProvider); + this(new HalFactory[]{factory}, audioSessionProvider); } @Override diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java index 5a06a2c4b388..12f9fd936fba 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java @@ -113,9 +113,9 @@ import java.util.Set; public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareService.Stub { static private final String TAG = "SoundTriggerMiddlewareService"; - final ISoundTriggerMiddlewareService mDelegate; - final Context mContext; - Set<Integer> mModuleHandles; + private final ISoundTriggerMiddlewareService mDelegate; + private final Context mContext; + private Set<Integer> mModuleHandles; /** * Constructor for internal use only. Could be exposed for testing purposes in the future. @@ -280,7 +280,7 @@ public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareServic } /** Activity state. */ - public Activity activityState = Activity.LOADED; + Activity activityState = Activity.LOADED; /** * A map of known parameter support. A missing key means we don't know yet whether the @@ -294,7 +294,7 @@ public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareServic * * @param modelParam The parameter key. */ - public void checkSupported(int modelParam) { + void checkSupported(int modelParam) { if (!parameterSupport.containsKey(modelParam)) { throw new IllegalStateException("Parameter has not been checked for support."); } @@ -311,7 +311,7 @@ public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareServic * @param modelParam The parameter key. * @param value The value. */ - public void checkSupported(int modelParam, int value) { + void checkSupported(int modelParam, int value) { if (!parameterSupport.containsKey(modelParam)) { throw new IllegalStateException("Parameter has not been checked for support."); } @@ -329,7 +329,7 @@ public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareServic * @param modelParam The parameter key. * @param range The parameter value range, or null if not supported. */ - public void updateParameterSupport(int modelParam, @Nullable ModelParameterRange range) { + void updateParameterSupport(int modelParam, @Nullable ModelParameterRange range) { parameterSupport.put(modelParam, range); } } @@ -338,27 +338,26 @@ public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareServic * Entry-point to this module: exposes the module as a {@link SystemService}. */ public static final class Lifecycle extends SystemService { - private SoundTriggerMiddlewareService mService; - public Lifecycle(Context context) { super(context); } @Override public void onStart() { - ISoundTriggerHw[] services; - try { - services = new ISoundTriggerHw[]{ISoundTriggerHw.getService(true)}; - Log.d(TAG, "Connected to default ISoundTriggerHw"); - } catch (Exception e) { - Log.e(TAG, "Failed to connect to default ISoundTriggerHw", e); - services = new ISoundTriggerHw[0]; - } + HalFactory[] factories = new HalFactory[]{() -> { + try { + Log.d(TAG, "Connecting to default ISoundTriggerHw"); + return ISoundTriggerHw.getService(true); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + }}; - mService = new SoundTriggerMiddlewareService( - new SoundTriggerMiddlewareImpl(services, new AudioSessionProviderImpl()), - getContext()); - publishBinderService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE, mService); + publishBinderService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE, + new SoundTriggerMiddlewareService( + new SoundTriggerMiddlewareImpl(factories, + new AudioSessionProviderImpl()), + getContext())); } } @@ -370,7 +369,7 @@ public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareServic DeathRecipient { private final ISoundTriggerCallback mCallback; private ISoundTriggerModule mDelegate; - private Map<Integer, ModelState> mLoadedModels = new HashMap<>(); + private @NonNull Map<Integer, ModelState> mLoadedModels = new HashMap<>(); ModuleService(@NonNull ISoundTriggerCallback callback) { mCallback = callback; @@ -680,7 +679,7 @@ public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareServic } catch (RemoteException e) { // Dead client will be handled by binderDied() - no need to handle here. // In any case, client callbacks are considered best effort. - Log.e(TAG, "Client callback execption.", e); + Log.e(TAG, "Client callback exception.", e); } } } @@ -696,20 +695,33 @@ public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareServic } catch (RemoteException e) { // Dead client will be handled by binderDied() - no need to handle here. // In any case, client callbacks are considered best effort. - Log.e(TAG, "Client callback execption.", e); + Log.e(TAG, "Client callback exception.", e); } } } @Override - public void onRecognitionAvailabilityChange(boolean available) throws RemoteException { + public void onRecognitionAvailabilityChange(boolean available) { synchronized (this) { try { mCallback.onRecognitionAvailabilityChange(available); } catch (RemoteException e) { // Dead client will be handled by binderDied() - no need to handle here. // In any case, client callbacks are considered best effort. - Log.e(TAG, "Client callback execption.", e); + Log.e(TAG, "Client callback exception.", e); + } + } + } + + @Override + public void onModuleDied() { + synchronized (this) { + try { + mCallback.onModuleDied(); + } catch (RemoteException e) { + // Dead client will be handled by binderDied() - no need to handle here. + // In any case, client callbacks are considered best effort. + Log.e(TAG, "Client callback exception.", e); } } } diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java index f024edecab3b..adf16fafc000 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java @@ -30,7 +30,9 @@ import android.media.soundtrigger_middleware.SoundModelType; import android.media.soundtrigger_middleware.SoundTriggerModuleProperties; import android.media.soundtrigger_middleware.Status; import android.os.IBinder; +import android.os.IHwBinder; import android.os.RemoteException; +import android.os.ServiceSpecificException; import android.util.Log; import java.util.HashMap; @@ -57,6 +59,7 @@ import java.util.Set; * gracefully handle driver malfunction and such behavior will result in undefined behavior. If this * service is to used with an untrusted driver, the driver must be wrapped with validation / error * recovery code. + * <li>Recovery from driver death is supported.</li> * <li>RemoteExceptions thrown by the driver are treated as RuntimeExceptions - they are not * considered recoverable faults and should not occur in a properly functioning system. * <li>There is no binder instance associated with this implementation. Do not call asBinder(). @@ -79,27 +82,29 @@ import java.util.Set; * * @hide */ -class SoundTriggerModule { +class SoundTriggerModule implements IHwBinder.DeathRecipient { static private final String TAG = "SoundTriggerModule"; - @NonNull private final ISoundTriggerHw2 mHalService; + @NonNull private HalFactory mHalFactory; + @NonNull private ISoundTriggerHw2 mHalService; @NonNull private final SoundTriggerMiddlewareImpl.AudioSessionProvider mAudioSessionProvider; private final Set<Session> mActiveSessions = new HashSet<>(); private int mNumLoadedModels = 0; - private SoundTriggerModuleProperties mProperties = null; + private SoundTriggerModuleProperties mProperties; private boolean mRecognitionAvailable; /** * Ctor. * - * @param halService The underlying HAL driver. + * @param halFactory A factory for the underlying HAL driver. */ - SoundTriggerModule(@NonNull android.hardware.soundtrigger.V2_0.ISoundTriggerHw halService, + SoundTriggerModule(@NonNull HalFactory halFactory, @NonNull SoundTriggerMiddlewareImpl.AudioSessionProvider audioSessionProvider) { - assert halService != null; - mHalService = new SoundTriggerHw2Compat(halService); + assert halFactory != null; + mHalFactory = halFactory; mAudioSessionProvider = audioSessionProvider; - mProperties = ConversionUtil.hidl2aidlProperties(mHalService.getProperties()); + attachToHal(); + mProperties = ConversionUtil.hidl2aidlProperties(mHalService.getProperties()); // We conservatively assume that external capture is active until explicitly told otherwise. mRecognitionAvailable = mProperties.concurrentCapture; } @@ -117,7 +122,7 @@ class SoundTriggerModule { * @return The interface through which this module can be controlled. */ synchronized @NonNull - Session attach(@NonNull ISoundTriggerCallback callback) { + ISoundTriggerModule attach(@NonNull ISoundTriggerCallback callback) { Log.d(TAG, "attach()"); Session session = new Session(callback); mActiveSessions.add(session); @@ -145,6 +150,7 @@ class SoundTriggerModule { */ synchronized void setExternalCaptureState(boolean active) { Log.d(TAG, String.format("setExternalCaptureState(active=%b)", active)); + if (mProperties.concurrentCapture) { // If we support concurrent capture, we don't care about any of this. return; @@ -162,6 +168,23 @@ class SoundTriggerModule { } } + @Override + public synchronized void serviceDied(long cookie) { + Log.w(TAG, String.format("Underlying HAL driver died.")); + for (Session session : mActiveSessions) { + session.moduleDied(); + } + attachToHal(); + } + + /** + * Attached to the HAL service via factory. + */ + private void attachToHal() { + mHalService = new SoundTriggerHw2Compat(mHalFactory.create()); + mHalService.linkToDeath(this, 0); + } + /** * Remove session from the list of active sessions. * @@ -204,7 +227,11 @@ class SoundTriggerModule { public void detach() { Log.d(TAG, "detach()"); synchronized (SoundTriggerModule.this) { + if (mCallback == null) { + return; + } removeSession(this); + mCallback = null; } } @@ -212,6 +239,7 @@ class SoundTriggerModule { public int loadModel(@NonNull SoundModel model) { Log.d(TAG, String.format("loadModel(model=%s)", model)); synchronized (SoundTriggerModule.this) { + checkValid(); if (mNumLoadedModels == mProperties.maxSoundModels) { throw new RecoverableException(Status.RESOURCE_CONTENTION, "Maximum number of models loaded."); @@ -227,6 +255,7 @@ class SoundTriggerModule { public int loadPhraseModel(@NonNull PhraseSoundModel model) { Log.d(TAG, String.format("loadPhraseModel(model=%s)", model)); synchronized (SoundTriggerModule.this) { + checkValid(); if (mNumLoadedModels == mProperties.maxSoundModels) { throw new RecoverableException(Status.RESOURCE_CONTENTION, "Maximum number of models loaded."); @@ -243,6 +272,7 @@ class SoundTriggerModule { public void unloadModel(int modelHandle) { Log.d(TAG, String.format("unloadModel(handle=%d)", modelHandle)); synchronized (SoundTriggerModule.this) { + checkValid(); mLoadedModels.get(modelHandle).unload(); --mNumLoadedModels; } @@ -253,6 +283,7 @@ class SoundTriggerModule { Log.d(TAG, String.format("startRecognition(handle=%d, config=%s)", modelHandle, config)); synchronized (SoundTriggerModule.this) { + checkValid(); mLoadedModels.get(modelHandle).startRecognition(config); } } @@ -269,26 +300,28 @@ class SoundTriggerModule { public void forceRecognitionEvent(int modelHandle) { Log.d(TAG, String.format("forceRecognitionEvent(handle=%d)", modelHandle)); synchronized (SoundTriggerModule.this) { + checkValid(); mLoadedModels.get(modelHandle).forceRecognitionEvent(); } } @Override - public void setModelParameter(int modelHandle, int modelParam, int value) - throws RemoteException { + public void setModelParameter(int modelHandle, int modelParam, int value) { Log.d(TAG, String.format("setModelParameter(handle=%d, param=%d, value=%d)", modelHandle, modelParam, value)); synchronized (SoundTriggerModule.this) { + checkValid(); mLoadedModels.get(modelHandle).setParameter(modelParam, value); } } @Override - public int getModelParameter(int modelHandle, int modelParam) throws RemoteException { + public int getModelParameter(int modelHandle, int modelParam) { Log.d(TAG, String.format("getModelParameter(handle=%d, param=%d)", modelHandle, modelParam)); synchronized (SoundTriggerModule.this) { + checkValid(); return mLoadedModels.get(modelHandle).getParameter(modelParam); } } @@ -299,6 +332,7 @@ class SoundTriggerModule { Log.d(TAG, String.format("queryModelParameterSupport(handle=%d, param=%d)", modelHandle, modelParam)); synchronized (SoundTriggerModule.this) { + checkValid(); return mLoadedModels.get(modelHandle).queryModelParameterSupport(modelParam); } } @@ -322,6 +356,25 @@ class SoundTriggerModule { } } + /** + * The underlying module HAL is dead. + */ + private void moduleDied() { + try { + mCallback.onModuleDied(); + removeSession(this); + mCallback = null; + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + + private void checkValid() { + if (mCallback == null) { + throw new ServiceSpecificException(Status.DEAD_OBJECT); + } + } + @Override public @NonNull IBinder asBinder() { @@ -350,10 +403,6 @@ class SoundTriggerModule { SoundTriggerModule.this.notifyAll(); } - private void waitStateChange() throws InterruptedException { - SoundTriggerModule.this.wait(); - } - private int load(@NonNull SoundModel model) { mModelType = model.type; ISoundTriggerHw.SoundModel hidlModel = ConversionUtil.aidl2hidlSoundModel(model); diff --git a/services/core/java/com/android/server/stats/OWNERS b/services/core/java/com/android/server/stats/OWNERS index 8d7f8822f78e..fc7fd220b26a 100644 --- a/services/core/java/com/android/server/stats/OWNERS +++ b/services/core/java/com/android/server/stats/OWNERS @@ -1,9 +1,8 @@ -bookatz@google.com -cjyu@google.com -dwchen@google.com +jeffreyhuang@google.com joeo@google.com +muhammadq@google.com +ruchirr@google.com singhtejinder@google.com -stlafon@google.com +tsaichristine@google.com yaochen@google.com -yanglu@google.com -yro@google.com
\ No newline at end of file +yro@google.com diff --git a/services/core/java/com/android/server/stats/StatsPullAtomService.java b/services/core/java/com/android/server/stats/StatsPullAtomService.java deleted file mode 100644 index a75f1c210bc0..000000000000 --- a/services/core/java/com/android/server/stats/StatsPullAtomService.java +++ /dev/null @@ -1,840 +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 com.android.server.stats; - -import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED; -import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED; -import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS; -import static android.os.Process.THREAD_PRIORITY_BACKGROUND; -import static android.os.Process.getUidForPid; -import static android.os.storage.VolumeInfo.TYPE_PRIVATE; -import static android.os.storage.VolumeInfo.TYPE_PUBLIC; - -import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem; -import static com.android.server.stats.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs; -import static com.android.server.stats.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs; -import static com.android.server.stats.ProcfsMemoryUtil.forEachPid; -import static com.android.server.stats.ProcfsMemoryUtil.readCmdlineFromProcfs; -import static com.android.server.stats.ProcfsMemoryUtil.readMemorySnapshotFromProcfs; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.ActivityManagerInternal; -import android.app.AlarmManager; -import android.app.AlarmManager.OnAlarmListener; -import android.app.AppOpsManager; -import android.app.AppOpsManager.HistoricalOps; -import android.app.AppOpsManager.HistoricalOpsRequest; -import android.app.AppOpsManager.HistoricalPackageOps; -import android.app.AppOpsManager.HistoricalUidOps; -import android.app.INotificationManager; -import android.app.ProcessMemoryState; -import android.app.StatsManager; -import android.app.StatsManager.PullAtomMetadata; -import android.bluetooth.BluetoothActivityEnergyInfo; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.UidTraffic; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.PermissionInfo; -import android.content.pm.UserInfo; -import android.hardware.biometrics.BiometricsProtoEnums; -import android.hardware.face.FaceManager; -import android.hardware.fingerprint.FingerprintManager; -import android.net.ConnectivityManager; -import android.net.INetworkStatsService; -import android.net.Network; -import android.net.NetworkRequest; -import android.net.NetworkStats; -import android.net.wifi.WifiManager; -import android.os.BatteryStats; -import android.os.BatteryStatsInternal; -import android.os.Binder; -import android.os.Build; -import android.os.Bundle; -import android.os.CoolingDevice; -import android.os.Environment; -import android.os.FileUtils; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.IBinder; -import android.os.IPullAtomCallback; -import android.os.IStatsCompanionService; -import android.os.IStatsd; -import android.os.IStoraged; -import android.os.IThermalEventListener; -import android.os.IThermalService; -import android.os.Looper; -import android.os.ParcelFileDescriptor; -import android.os.Parcelable; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.StatFs; -import android.os.StatsLogEventWrapper; -import android.os.SynchronousResultReceiver; -import android.os.SystemClock; -import android.os.SystemProperties; -import android.os.Temperature; -import android.os.UserHandle; -import android.os.UserManager; -import android.os.connectivity.WifiActivityEnergyInfo; -import android.os.storage.DiskInfo; -import android.os.storage.StorageManager; -import android.os.storage.VolumeInfo; -import android.provider.Settings; -import android.stats.storage.StorageEnums; -import android.telephony.ModemActivityInfo; -import android.telephony.TelephonyManager; -import android.util.ArrayMap; -import android.util.ArraySet; -import android.util.Log; -import android.util.Slog; -import android.util.StatsEvent; -import android.util.StatsLog; -import android.util.proto.ProtoOutputStream; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.app.procstats.IProcessStats; -import com.android.internal.app.procstats.ProcessStats; -import com.android.internal.os.BackgroundThread; -import com.android.internal.os.BatterySipper; -import com.android.internal.os.BatteryStatsHelper; -import com.android.internal.os.BinderCallsStats.ExportedCallStat; -import com.android.internal.os.KernelCpuSpeedReader; -import com.android.internal.os.KernelCpuThreadReader; -import com.android.internal.os.KernelCpuThreadReaderDiff; -import com.android.internal.os.KernelCpuThreadReaderSettingsObserver; -import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader; -import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader; -import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader; -import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader; -import com.android.internal.os.KernelWakelockReader; -import com.android.internal.os.KernelWakelockStats; -import com.android.internal.os.LooperStats; -import com.android.internal.os.PowerProfile; -import com.android.internal.os.ProcessCpuTracker; -import com.android.internal.os.StoragedUidIoStatsReader; -import com.android.internal.util.DumpUtils; -import com.android.server.BinderCallsStatsService; -import com.android.server.LocalServices; -import com.android.server.SystemService; -import com.android.server.SystemServiceManager; -import com.android.server.am.MemoryStatUtil.MemoryStat; -import com.android.server.notification.NotificationManagerService; -import com.android.server.role.RoleManagerInternal; -import com.android.server.stats.IonMemoryUtil.IonAllocations; -import com.android.server.stats.ProcfsMemoryUtil.MemorySnapshot; -import com.android.server.storage.DiskStatsFileLogger; -import com.android.server.storage.DiskStatsLoggingService; - -import com.google.android.collect.Sets; - -import libcore.io.IoUtils; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.File; -import java.io.FileDescriptor; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -/** - * SystemService containing PullAtomCallbacks that are registered with statsd. - * - * @hide - */ -public class StatsPullAtomService extends SystemService { - private static final String TAG = "StatsPullAtomService"; - private static final boolean DEBUG = true; - - private final Object mNetworkStatsLock = new Object(); - @GuardedBy("mNetworkStatsLock") - private INetworkStatsService mNetworkStatsService; - private final Object mThermalLock = new Object(); - @GuardedBy("mThermalLock") - private IThermalService mThermalService; - - private final Context mContext; - private StatsManager mStatsManager; - - public StatsPullAtomService(Context context) { - super(context); - mContext = context; - } - - @Override - public void onStart() { - mStatsManager = (StatsManager) mContext.getSystemService(Context.STATS_MANAGER); - } - - @Override - public void onBootPhase(int phase) { - super.onBootPhase(phase); - if (phase == PHASE_SYSTEM_SERVICES_READY) { - BackgroundThread.getHandler().post(() -> { - registerAllPullers(); - }); - } - } - - void registerAllPullers() { - if (DEBUG) { - Slog.d(TAG, "Registering all pullers with statsd"); - } - registerWifiBytesTransfer(); - registerWifiBytesTransferBackground(); - registerMobileBytesTransfer(); - registerMobileBytesTransferBackground(); - registerBluetoothBytesTransfer(); - registerKernelWakelock(); - registerCpuTimePerFreq(); - registerCpuTimePerUid(); - registerCpuTimePerUidFreq(); - registerCpuActiveTime(); - registerCpuClusterTime(); - registerWifiActivityInfo(); - registerModemActivityInfo(); - registerBluetoothActivityInfo(); - registerSystemElapsedRealtime(); - registerSystemUptime(); - registerRemainingBatteryCapacity(); - registerFullBatteryCapacity(); - registerBatteryVoltage(); - registerBatteryLevel(); - registerBatteryCycleCount(); - registerProcessMemoryState(); - registerProcessMemoryHighWaterMark(); - registerProcessMemorySnapshot(); - registerSystemIonHeapSize(); - registerProcessSystemIonHeapSize(); - registerTemperature(); - registerCoolingDevice(); - registerBinderCalls(); - registerBinderCallsExceptions(); - registerLooperStats(); - registerDiskStats(); - registerDirectoryUsage(); - registerAppSize(); - registerCategorySize(); - registerNumFingerprintsEnrolled(); - registerNumFacesEnrolled(); - registerProcStats(); - registerProcStatsPkgProc(); - registerDiskIO(); - registerPowerProfile(); - registerProcessCpuTime(); - registerCpuTimePerThreadFreq(); - registerDeviceCalculatedPowerUse(); - registerDeviceCalculatedPowerBlameUid(); - registerDeviceCalculatedPowerBlameOther(); - registerDebugElapsedClock(); - registerDebugFailingElapsedClock(); - registerBuildInformation(); - registerRoleHolder(); - registerDangerousPermissionState(); - registerTimeZoneDataInfo(); - registerExternalStorageInfo(); - registerAppsOnExternalStorageInfo(); - registerFaceSettings(); - registerAppOps(); - registerNotificationRemoteViews(); - registerDangerousPermissionState(); - registerDangerousPermissionStateSampled(); - } - - private INetworkStatsService getINetworkStatsService() { - synchronized (mNetworkStatsLock) { - if (mNetworkStatsService == null) { - mNetworkStatsService = INetworkStatsService.Stub.asInterface( - ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); - if (mNetworkStatsService != null) { - try { - mNetworkStatsService.asBinder().linkToDeath(() -> { - synchronized (mNetworkStatsLock) { - mNetworkStatsService = null; - } - }, /* flags */ 0); - } catch (RemoteException e) { - Slog.e(TAG, "linkToDeath with NetworkStatsService failed", e); - mNetworkStatsService = null; - } - } - - } - return mNetworkStatsService; - } - } - - private IThermalService getIThermalService() { - synchronized (mThermalLock) { - if (mThermalService == null) { - mThermalService = IThermalService.Stub.asInterface( - ServiceManager.getService(Context.THERMAL_SERVICE)); - if (mThermalService != null) { - try { - mThermalService.asBinder().linkToDeath(() -> { - synchronized (mThermalLock) { - mThermalService = null; - } - }, /* flags */ 0); - } catch (RemoteException e) { - Slog.e(TAG, "linkToDeath with thermalService failed", e); - mThermalService = null; - } - } - } - return mThermalService; - } - } - private void registerWifiBytesTransfer() { - int tagId = StatsLog.WIFI_BYTES_TRANSFER; - PullAtomMetadata metaData = PullAtomMetadata.newBuilder() - .setAdditiveFields(new int[] {2, 3, 4, 5}).build(); - mStatsManager.registerPullAtomCallback( - tagId, - metaData, - (atomTag, data) -> pullWifiBytesTransfer(atomTag, data), - Executors.newSingleThreadExecutor() - ); - } - - private int pullWifiBytesTransfer(int atomTag, List<StatsEvent> pulledData) { - INetworkStatsService networkStatsService = getINetworkStatsService(); - if (networkStatsService == null) { - Slog.e(TAG, "NetworkStats Service is not available!"); - return StatsManager.PULL_SKIP; - } - long token = Binder.clearCallingIdentity(); - try { - // TODO: Consider caching the following call to get BatteryStatsInternal. - BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class); - String[] ifaces = bs.getWifiIfaces(); - if (ifaces.length == 0) { - return StatsManager.PULL_SKIP; - } - // Combine all the metrics per Uid into one record. - NetworkStats stats = networkStatsService.getDetailedUidStats(ifaces).groupedByUid(); - addNetworkStats(atomTag, pulledData, stats, false); - } catch (RemoteException e) { - Slog.e(TAG, "Pulling netstats for wifi bytes has error", e); - } finally { - Binder.restoreCallingIdentity(token); - } - return StatsManager.PULL_SUCCESS; - } - - private void addNetworkStats( - int tag, List<StatsEvent> ret, NetworkStats stats, boolean withFGBG) { - int size = stats.size(); - NetworkStats.Entry entry = new NetworkStats.Entry(); // For recycling - for (int j = 0; j < size; j++) { - stats.getValues(j, entry); - StatsEvent.Builder e = StatsEvent.newBuilder(); - e.setAtomId(tag); - e.writeInt(entry.uid); - if (withFGBG) { - e.writeInt(entry.set); - } - e.writeLong(entry.rxBytes); - e.writeLong(entry.rxPackets); - e.writeLong(entry.txBytes); - e.writeLong(entry.txPackets); - ret.add(e.build()); - } - } - - private void registerWifiBytesTransferBackground() { - // No op. - } - - private void pullWifiBytesTransferBackground() { - // No op. - } - - private void registerMobileBytesTransfer() { - // No op. - } - - private void pullMobileBytesTransfer() { - // No op. - } - - private void registerMobileBytesTransferBackground() { - // No op. - } - - private void pullMobileBytesTransferBackground() { - // No op. - } - - private void registerBluetoothBytesTransfer() { - // No op. - } - - private void pullBluetoothBytesTransfer() { - // No op. - } - - private void registerKernelWakelock() { - // No op. - } - - private void pullKernelWakelock() { - // No op. - } - - private void registerCpuTimePerFreq() { - // No op. - } - - private void pullCpuTimePerFreq() { - // No op. - } - - private void registerCpuTimePerUid() { - // No op. - } - - private void pullCpuTimePerUid() { - // No op. - } - - private void registerCpuTimePerUidFreq() { - // No op. - } - - private void pullCpuTimeperUidFreq() { - // No op. - } - - private void registerCpuActiveTime() { - // No op. - } - - private void pullCpuActiveTime() { - // No op. - } - - private void registerCpuClusterTime() { - // No op. - } - - private int pullCpuClusterTime() { - return 0; - } - - private void registerWifiActivityInfo() { - // No op. - } - - private void pullWifiActivityInfo() { - // No op. - } - - private void registerModemActivityInfo() { - // No op. - } - - private void pullModemActivityInfo() { - // No op. - } - - private void registerBluetoothActivityInfo() { - // No op. - } - - private void pullBluetoothActivityInfo() { - // No op. - } - - private void registerSystemElapsedRealtime() { - // No op. - } - - private void pullSystemElapsedRealtime() { - // No op. - } - - private void registerSystemUptime() { - // No op. - } - - private void pullSystemUptime() { - // No op. - } - - private void registerRemainingBatteryCapacity() { - // No op. - } - - private void pullRemainingBatteryCapacity() { - // No op. - } - - private void registerFullBatteryCapacity() { - // No op. - } - - private void pullFullBatteryCapacity() { - // No op. - } - - private void registerBatteryVoltage() { - // No op. - } - - private void pullBatteryVoltage() { - // No op. - } - - private void registerBatteryLevel() { - // No op. - } - - private void pullBatteryLevel() { - // No op. - } - - private void registerBatteryCycleCount() { - // No op. - } - - private void pullBatteryCycleCount() { - // No op. - } - - private void registerProcessMemoryState() { - // No op. - } - - private void pullProcessMemoryState() { - // No op. - } - - private void registerProcessMemoryHighWaterMark() { - // No op. - } - - private void pullProcessMemoryHighWaterMark() { - // No op. - } - - private void registerProcessMemorySnapshot() { - // No op. - } - - private void pullProcessMemorySnapshot() { - // No op. - } - - private void registerSystemIonHeapSize() { - // No op. - } - - private void pullSystemIonHeapSize() { - // No op. - } - - private void registerProcessSystemIonHeapSize() { - // No op. - } - - private void pullProcessSystemIonHeapSize() { - // No op. - } - - private void registerTemperature() { - // No op. - } - - private void pullTemperature() { - // No op. - } - - private void registerCoolingDevice() { - // No op. - } - - private void pullCooldownDevice() { - // No op. - } - - private void registerBinderCalls() { - // No op. - } - - private void pullBinderCalls() { - // No op. - } - - private void registerBinderCallsExceptions() { - // No op. - } - - private void pullBinderCallsExceptions() { - // No op. - } - - private void registerLooperStats() { - // No op. - } - - private void pullLooperStats() { - // No op. - } - - private void registerDiskStats() { - // No op. - } - - private void pullDiskStats() { - // No op. - } - - private void registerDirectoryUsage() { - // No op. - } - - private void pullDirectoryUsage() { - // No op. - } - - private void registerAppSize() { - // No op. - } - - private void pullAppSize() { - // No op. - } - - private void registerCategorySize() { - // No op. - } - - private void pullCategorySize() { - // No op. - } - - private void registerNumFingerprintsEnrolled() { - // No op. - } - - private void pullNumFingerprintsEnrolled() { - // No op. - } - - private void registerNumFacesEnrolled() { - // No op. - } - - private void pullNumFacesEnrolled() { - // No op. - } - - private void registerProcStats() { - // No op. - } - - private void pullProcStats() { - // No op. - } - - private void registerProcStatsPkgProc() { - // No op. - } - - private void pullProcStatsPkgProc() { - // No op. - } - - private void registerDiskIO() { - // No op. - } - - private void pullDiskIO() { - // No op. - } - - private void registerPowerProfile() { - // No op. - } - - private void pullPowerProfile() { - // No op. - } - - private void registerProcessCpuTime() { - // No op. - } - - private void pullProcessCpuTime() { - // No op. - } - - private void registerCpuTimePerThreadFreq() { - // No op. - } - - private void pullCpuTimePerThreadFreq() { - // No op. - } - - private void registerDeviceCalculatedPowerUse() { - // No op. - } - - private void pullDeviceCalculatedPowerUse() { - // No op. - } - - private void registerDeviceCalculatedPowerBlameUid() { - // No op. - } - - private void pullDeviceCalculatedPowerBlameUid() { - // No op. - } - - private void registerDeviceCalculatedPowerBlameOther() { - // No op. - } - - private void pullDeviceCalculatedPowerBlameOther() { - // No op. - } - - private void registerDebugElapsedClock() { - // No op. - } - - private void pullDebugElapsedClock() { - // No op. - } - - private void registerDebugFailingElapsedClock() { - // No op. - } - - private void pullDebugFailingElapsedClock() { - // No op. - } - - private void registerBuildInformation() { - // No op. - } - - private void pullBuildInformation() { - // No op. - } - - private void registerRoleHolder() { - // No op. - } - - private void pullRoleHolder() { - // No op. - } - - private void registerDangerousPermissionState() { - // No op. - } - - private void pullDangerousPermissionState() { - // No op. - } - - private void registerTimeZoneDataInfo() { - // No op. - } - - private void pullTimeZoneDataInfo() { - // No op. - } - - private void registerExternalStorageInfo() { - // No op. - } - - private void pullExternalStorageInfo() { - // No op. - } - - private void registerAppsOnExternalStorageInfo() { - // No op. - } - - private void pullAppsOnExternalStorageInfo() { - // No op. - } - - private void registerFaceSettings() { - // No op. - } - - private void pullRegisterFaceSettings() { - // No op. - } - - private void registerAppOps() { - // No op. - } - - private void pullAppOps() { - // No op. - } - - private void registerNotificationRemoteViews() { - // No op. - } - - private void pullNotificationRemoteViews() { - // No op. - } - - private void registerDangerousPermissionStateSampled() { - // No op. - } - - private void pullDangerousPermissionStateSampled() { - // No op. - } -} diff --git a/services/core/java/com/android/server/stats/IonMemoryUtil.java b/services/core/java/com/android/server/stats/pull/IonMemoryUtil.java index c9be96f08876..fde0a590df34 100644 --- a/services/core/java/com/android/server/stats/IonMemoryUtil.java +++ b/services/core/java/com/android/server/stats/pull/IonMemoryUtil.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.server.stats; +package com.android.server.stats.pull; import android.os.FileUtils; import android.util.Slog; @@ -30,8 +30,11 @@ import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; -/** Utility methods for reading ion memory stats. */ -final class IonMemoryUtil { +/** + * Utility methods for reading ion memory stats. + * TODO: Consider making package private after puller migration + */ +public final class IonMemoryUtil { private static final String TAG = "IonMemoryUtil"; /** Path to debugfs file for the system ion heap. */ @@ -50,7 +53,7 @@ final class IonMemoryUtil { * Returns value of the total size in bytes of the system ion heap from * /sys/kernel/debug/ion/heaps/system. */ - static long readSystemIonHeapSizeFromDebugfs() { + public static long readSystemIonHeapSizeFromDebugfs() { return parseIonHeapSizeFromDebugfs(readFile(DEBUG_SYSTEM_ION_HEAP_FILE)); } @@ -78,7 +81,7 @@ final class IonMemoryUtil { * Returns values of allocation sizes in bytes on the system ion heap from * /sys/kernel/debug/ion/heaps/system. */ - static List<IonAllocations> readProcessSystemIonHeapSizesFromDebugfs() { + public static List<IonAllocations> readProcessSystemIonHeapSizesFromDebugfs() { return parseProcessIonHeapSizesFromDebugfs(readFile(DEBUG_SYSTEM_ION_HEAP_FILE)); } @@ -130,7 +133,7 @@ final class IonMemoryUtil { } /** Summary information about process ion allocations. */ - static final class IonAllocations { + public static final class IonAllocations { /** PID these allocations belong to. */ public int pid; /** Size of all individual allocations added together. */ diff --git a/services/core/java/com/android/server/stats/ProcfsMemoryUtil.java b/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java index c1eacce1f304..638dfd23c27f 100644 --- a/services/core/java/com/android/server/stats/ProcfsMemoryUtil.java +++ b/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.server.stats; +package com.android.server.stats.pull; import static android.os.Process.PROC_OUT_STRING; @@ -22,7 +22,7 @@ import android.os.Process; import java.util.function.BiConsumer; -final class ProcfsMemoryUtil { +public final class ProcfsMemoryUtil { private static final int[] CMDLINE_OUT = new int[] { PROC_OUT_STRING }; private static final String[] STATUS_KEYS = new String[] { "Uid:", @@ -39,7 +39,7 @@ final class ProcfsMemoryUtil { * VmSwap fields in /proc/pid/status in kilobytes or null if not available. */ @Nullable - static MemorySnapshot readMemorySnapshotFromProcfs(int pid) { + public static MemorySnapshot readMemorySnapshotFromProcfs(int pid) { long[] output = new long[STATUS_KEYS.length]; output[0] = -1; Process.readProcLines("/proc/" + pid + "/status", STATUS_KEYS, output); @@ -63,7 +63,7 @@ final class ProcfsMemoryUtil { * Returns content of /proc/pid/cmdline (e.g. /system/bin/statsd) or an empty string * if the file is not available. */ - static String readCmdlineFromProcfs(int pid) { + public static String readCmdlineFromProcfs(int pid) { String[] cmdline = new String[1]; if (!Process.readProcFile("/proc/" + pid + "/cmdline", CMDLINE_OUT, cmdline, null, null)) { return ""; @@ -71,7 +71,7 @@ final class ProcfsMemoryUtil { return cmdline[0]; } - static void forEachPid(BiConsumer<Integer, String> func) { + public static void forEachPid(BiConsumer<Integer, String> func) { int[] pids = new int[1024]; pids = Process.getPids("/proc", pids); for (int pid : pids) { @@ -86,7 +86,7 @@ final class ProcfsMemoryUtil { } } - static final class MemorySnapshot { + public static final class MemorySnapshot { public int uid; public int rssHighWaterMarkInKilobytes; public int rssInKilobytes; diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java new file mode 100644 index 000000000000..3bc860adff0c --- /dev/null +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -0,0 +1,1777 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.stats.pull; + +import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED; +import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED; +import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS; +import static android.os.Debug.getIonHeapsSizeKb; +import static android.os.Process.THREAD_PRIORITY_BACKGROUND; +import static android.os.Process.getUidForPid; +import static android.os.storage.VolumeInfo.TYPE_PRIVATE; +import static android.os.storage.VolumeInfo.TYPE_PUBLIC; + +import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem; +import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs; +import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs; +import static com.android.server.stats.pull.ProcfsMemoryUtil.forEachPid; +import static com.android.server.stats.pull.ProcfsMemoryUtil.readCmdlineFromProcfs; +import static com.android.server.stats.pull.ProcfsMemoryUtil.readMemorySnapshotFromProcfs; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityManagerInternal; +import android.app.AlarmManager; +import android.app.AlarmManager.OnAlarmListener; +import android.app.AppOpsManager; +import android.app.AppOpsManager.HistoricalOps; +import android.app.AppOpsManager.HistoricalOpsRequest; +import android.app.AppOpsManager.HistoricalPackageOps; +import android.app.AppOpsManager.HistoricalUidOps; +import android.app.INotificationManager; +import android.app.ProcessMemoryState; +import android.app.StatsManager; +import android.app.StatsManager.PullAtomMetadata; +import android.bluetooth.BluetoothActivityEnergyInfo; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.UidTraffic; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PermissionInfo; +import android.content.pm.UserInfo; +import android.hardware.biometrics.BiometricsProtoEnums; +import android.hardware.face.FaceManager; +import android.hardware.fingerprint.FingerprintManager; +import android.net.ConnectivityManager; +import android.net.INetworkStatsService; +import android.net.Network; +import android.net.NetworkRequest; +import android.net.NetworkStats; +import android.net.wifi.WifiManager; +import android.os.BatteryStats; +import android.os.BatteryStatsInternal; +import android.os.Binder; +import android.os.Build; +import android.os.Bundle; +import android.os.CoolingDevice; +import android.os.Environment; +import android.os.FileUtils; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.IPullAtomCallback; +import android.os.IStatsCompanionService; +import android.os.IStatsd; +import android.os.IStoraged; +import android.os.IThermalEventListener; +import android.os.IThermalService; +import android.os.Looper; +import android.os.ParcelFileDescriptor; +import android.os.Parcelable; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.StatFs; +import android.os.StatsLogEventWrapper; +import android.os.SynchronousResultReceiver; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.os.Temperature; +import android.os.UserHandle; +import android.os.UserManager; +import android.os.connectivity.WifiActivityEnergyInfo; +import android.os.storage.DiskInfo; +import android.os.storage.StorageManager; +import android.os.storage.VolumeInfo; +import android.provider.Settings; +import android.stats.storage.StorageEnums; +import android.telephony.ModemActivityInfo; +import android.telephony.TelephonyManager; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Log; +import android.util.Slog; +import android.util.StatsEvent; +import android.util.StatsLog; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.app.procstats.IProcessStats; +import com.android.internal.app.procstats.ProcessStats; +import com.android.internal.os.BackgroundThread; +import com.android.internal.os.BatterySipper; +import com.android.internal.os.BatteryStatsHelper; +import com.android.internal.os.BinderCallsStats.ExportedCallStat; +import com.android.internal.os.KernelCpuSpeedReader; +import com.android.internal.os.KernelCpuThreadReader; +import com.android.internal.os.KernelCpuThreadReaderDiff; +import com.android.internal.os.KernelCpuThreadReaderSettingsObserver; +import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader; +import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader; +import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader; +import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader; +import com.android.internal.os.KernelWakelockReader; +import com.android.internal.os.KernelWakelockStats; +import com.android.internal.os.LooperStats; +import com.android.internal.os.PowerProfile; +import com.android.internal.os.ProcessCpuTracker; +import com.android.internal.os.StoragedUidIoStatsReader; +import com.android.internal.util.DumpUtils; +import com.android.server.BinderCallsStatsService; +import com.android.server.LocalServices; +import com.android.server.SystemService; +import com.android.server.SystemServiceManager; +import com.android.server.am.MemoryStatUtil.MemoryStat; +import com.android.server.notification.NotificationManagerService; +import com.android.server.role.RoleManagerInternal; +import com.android.server.stats.pull.IonMemoryUtil.IonAllocations; +import com.android.server.stats.pull.ProcfsMemoryUtil.MemorySnapshot; +import com.android.server.storage.DiskStatsFileLogger; +import com.android.server.storage.DiskStatsLoggingService; + +import com.google.android.collect.Sets; + +import libcore.io.IoUtils; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * SystemService containing PullAtomCallbacks that are registered with statsd. + * + * @hide + */ +public class StatsPullAtomService extends SystemService { + private static final String TAG = "StatsPullAtomService"; + private static final boolean DEBUG = true; + + private static final String RESULT_RECEIVER_CONTROLLER_KEY = "controller_activity"; + /** + * How long to wait on an individual subsystem to return its stats. + */ + private static final long EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS = 2000; + + private final Object mNetworkStatsLock = new Object(); + @GuardedBy("mNetworkStatsLock") + private INetworkStatsService mNetworkStatsService; + private final Object mThermalLock = new Object(); + @GuardedBy("mThermalLock") + private IThermalService mThermalService; + + private final Context mContext; + private StatsManager mStatsManager; + + public StatsPullAtomService(Context context) { + super(context); + mContext = context; + } + + @Override + public void onStart() { + mStatsManager = (StatsManager) mContext.getSystemService(Context.STATS_MANAGER); + mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); + mTelephony = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); + + // Used to initialize the CPU Frequency atom. + PowerProfile powerProfile = new PowerProfile(mContext); + final int numClusters = powerProfile.getNumCpuClusters(); + mKernelCpuSpeedReaders = new KernelCpuSpeedReader[numClusters]; + int firstCpuOfCluster = 0; + for (int i = 0; i < numClusters; i++) { + final int numSpeedSteps = powerProfile.getNumSpeedStepsInCpuCluster(i); + mKernelCpuSpeedReaders[i] = new KernelCpuSpeedReader(firstCpuOfCluster, + numSpeedSteps); + firstCpuOfCluster += powerProfile.getNumCoresInCpuCluster(i); + } + } + + @Override + public void onBootPhase(int phase) { + super.onBootPhase(phase); + if (phase == PHASE_SYSTEM_SERVICES_READY) { + BackgroundThread.getHandler().post(() -> { + registerAllPullers(); + }); + } + } + + void registerAllPullers() { + if (DEBUG) { + Slog.d(TAG, "Registering all pullers with statsd"); + } + registerWifiBytesTransfer(); + registerWifiBytesTransferBackground(); + registerMobileBytesTransfer(); + registerMobileBytesTransferBackground(); + registerBluetoothBytesTransfer(); + registerKernelWakelock(); + registerCpuTimePerFreq(); + registerCpuTimePerUid(); + registerCpuTimePerUidFreq(); + registerCpuActiveTime(); + registerCpuClusterTime(); + registerWifiActivityInfo(); + registerModemActivityInfo(); + registerBluetoothActivityInfo(); + registerSystemElapsedRealtime(); + registerSystemUptime(); + registerRemainingBatteryCapacity(); + registerFullBatteryCapacity(); + registerBatteryVoltage(); + registerBatteryLevel(); + registerBatteryCycleCount(); + registerProcessMemoryState(); + registerProcessMemoryHighWaterMark(); + registerProcessMemorySnapshot(); + registerSystemIonHeapSize(); + registerIonHeapSize(); + registerProcessSystemIonHeapSize(); + registerTemperature(); + registerCoolingDevice(); + registerBinderCallsStats(); + registerBinderCallsStatsExceptions(); + registerLooperStats(); + registerDiskStats(); + registerDirectoryUsage(); + registerAppSize(); + registerCategorySize(); + registerNumFingerprintsEnrolled(); + registerNumFacesEnrolled(); + registerProcStats(); + registerProcStatsPkgProc(); + registerDiskIO(); + registerPowerProfile(); + registerProcessCpuTime(); + registerCpuTimePerThreadFreq(); + registerDeviceCalculatedPowerUse(); + registerDeviceCalculatedPowerBlameUid(); + registerDeviceCalculatedPowerBlameOther(); + registerDebugElapsedClock(); + registerDebugFailingElapsedClock(); + registerBuildInformation(); + registerRoleHolder(); + registerDangerousPermissionState(); + registerTimeZoneDataInfo(); + registerExternalStorageInfo(); + registerAppsOnExternalStorageInfo(); + registerFaceSettings(); + registerAppOps(); + registerNotificationRemoteViews(); + registerDangerousPermissionState(); + registerDangerousPermissionStateSampled(); + } + + private INetworkStatsService getINetworkStatsService() { + synchronized (mNetworkStatsLock) { + if (mNetworkStatsService == null) { + mNetworkStatsService = INetworkStatsService.Stub.asInterface( + ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); + if (mNetworkStatsService != null) { + try { + mNetworkStatsService.asBinder().linkToDeath(() -> { + synchronized (mNetworkStatsLock) { + mNetworkStatsService = null; + } + }, /* flags */ 0); + } catch (RemoteException e) { + Slog.e(TAG, "linkToDeath with NetworkStatsService failed", e); + mNetworkStatsService = null; + } + } + + } + return mNetworkStatsService; + } + } + + private IThermalService getIThermalService() { + synchronized (mThermalLock) { + if (mThermalService == null) { + mThermalService = IThermalService.Stub.asInterface( + ServiceManager.getService(Context.THERMAL_SERVICE)); + if (mThermalService != null) { + try { + mThermalService.asBinder().linkToDeath(() -> { + synchronized (mThermalLock) { + mThermalService = null; + } + }, /* flags */ 0); + } catch (RemoteException e) { + Slog.e(TAG, "linkToDeath with thermalService failed", e); + mThermalService = null; + } + } + } + return mThermalService; + } + } + private void registerWifiBytesTransfer() { + int tagId = StatsLog.WIFI_BYTES_TRANSFER; + PullAtomMetadata metadata = PullAtomMetadata.newBuilder() + .setAdditiveFields(new int[] {2, 3, 4, 5}) + .build(); + mStatsManager.registerPullAtomCallback( + tagId, + metadata, + (atomTag, data) -> pullWifiBytesTransfer(atomTag, data), + Executors.newSingleThreadExecutor() + ); + } + + private int pullWifiBytesTransfer(int atomTag, List<StatsEvent> pulledData) { + INetworkStatsService networkStatsService = getINetworkStatsService(); + if (networkStatsService == null) { + Slog.e(TAG, "NetworkStats Service is not available!"); + return StatsManager.PULL_SKIP; + } + long token = Binder.clearCallingIdentity(); + try { + // TODO: Consider caching the following call to get BatteryStatsInternal. + BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class); + String[] ifaces = bs.getWifiIfaces(); + if (ifaces.length == 0) { + return StatsManager.PULL_SKIP; + } + // Combine all the metrics per Uid into one record. + NetworkStats stats = networkStatsService.getDetailedUidStats(ifaces).groupedByUid(); + addNetworkStats(atomTag, pulledData, stats, false); + } catch (RemoteException e) { + Slog.e(TAG, "Pulling netstats for wifi bytes has error", e); + return StatsManager.PULL_SKIP; + } finally { + Binder.restoreCallingIdentity(token); + } + return StatsManager.PULL_SUCCESS; + } + + private void addNetworkStats( + int tag, List<StatsEvent> ret, NetworkStats stats, boolean withFGBG) { + int size = stats.size(); + NetworkStats.Entry entry = new NetworkStats.Entry(); // For recycling + for (int j = 0; j < size; j++) { + stats.getValues(j, entry); + StatsEvent.Builder e = StatsEvent.newBuilder(); + e.setAtomId(tag); + e.writeInt(entry.uid); + if (withFGBG) { + e.writeInt(entry.set); + } + e.writeLong(entry.rxBytes); + e.writeLong(entry.rxPackets); + e.writeLong(entry.txBytes); + e.writeLong(entry.txPackets); + ret.add(e.build()); + } + } + + /** + * Allows rollups per UID but keeping the set (foreground/background) slicing. + * Adapted from groupedByUid in frameworks/base/core/java/android/net/NetworkStats.java + */ + private NetworkStats rollupNetworkStatsByFGBG(NetworkStats stats) { + final NetworkStats ret = new NetworkStats(stats.getElapsedRealtime(), 1); + + final NetworkStats.Entry entry = new NetworkStats.Entry(); + entry.iface = NetworkStats.IFACE_ALL; + entry.tag = NetworkStats.TAG_NONE; + entry.metered = NetworkStats.METERED_ALL; + entry.roaming = NetworkStats.ROAMING_ALL; + + int size = stats.size(); + NetworkStats.Entry recycle = new NetworkStats.Entry(); // Used for retrieving values + for (int i = 0; i < size; i++) { + stats.getValues(i, recycle); + + // Skip specific tags, since already counted in TAG_NONE + if (recycle.tag != NetworkStats.TAG_NONE) continue; + + entry.set = recycle.set; // Allows slicing by background/foreground + entry.uid = recycle.uid; + entry.rxBytes = recycle.rxBytes; + entry.rxPackets = recycle.rxPackets; + entry.txBytes = recycle.txBytes; + entry.txPackets = recycle.txPackets; + // Operations purposefully omitted since we don't use them for statsd. + ret.combineValues(entry); + } + return ret; + } + + private void registerWifiBytesTransferBackground() { + int tagId = StatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG; + PullAtomMetadata metadata = PullAtomMetadata.newBuilder() + .setAdditiveFields(new int[] {3, 4, 5, 6}) + .build(); + mStatsManager.registerPullAtomCallback( + tagId, + metadata, + (atomTag, data) -> pullWifiBytesTransferBackground(atomTag, data), + Executors.newSingleThreadExecutor() + ); + } + + private int pullWifiBytesTransferBackground(int atomTag, List<StatsEvent> pulledData) { + INetworkStatsService networkStatsService = getINetworkStatsService(); + if (networkStatsService == null) { + Slog.e(TAG, "NetworkStats Service is not available!"); + return StatsManager.PULL_SKIP; + } + long token = Binder.clearCallingIdentity(); + try { + BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class); + String[] ifaces = bs.getWifiIfaces(); + if (ifaces.length == 0) { + return StatsManager.PULL_SKIP; + } + NetworkStats stats = rollupNetworkStatsByFGBG( + networkStatsService.getDetailedUidStats(ifaces)); + addNetworkStats(atomTag, pulledData, stats, true); + } catch (RemoteException e) { + Slog.e(TAG, "Pulling netstats for wifi bytes w/ fg/bg has error", e); + return StatsManager.PULL_SKIP; + } finally { + Binder.restoreCallingIdentity(token); + } + return StatsManager.PULL_SUCCESS; + } + + private void registerMobileBytesTransfer() { + int tagId = StatsLog.MOBILE_BYTES_TRANSFER; + PullAtomMetadata metadata = PullAtomMetadata.newBuilder() + .setAdditiveFields(new int[] {2, 3, 4, 5}) + .build(); + mStatsManager.registerPullAtomCallback( + tagId, + metadata, + (atomTag, data) -> pullMobileBytesTransfer(atomTag, data), + Executors.newSingleThreadExecutor() + ); + } + + private int pullMobileBytesTransfer(int atomTag, List<StatsEvent> pulledData) { + INetworkStatsService networkStatsService = getINetworkStatsService(); + if (networkStatsService == null) { + Slog.e(TAG, "NetworkStats Service is not available!"); + return StatsManager.PULL_SKIP; + } + long token = Binder.clearCallingIdentity(); + try { + BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class); + String[] ifaces = bs.getMobileIfaces(); + if (ifaces.length == 0) { + return StatsManager.PULL_SKIP; + } + // Combine all the metrics per Uid into one record. + NetworkStats stats = networkStatsService.getDetailedUidStats(ifaces).groupedByUid(); + addNetworkStats(atomTag, pulledData, stats, false); + } catch (RemoteException e) { + Slog.e(TAG, "Pulling netstats for mobile bytes has error", e); + return StatsManager.PULL_SKIP; + } finally { + Binder.restoreCallingIdentity(token); + } + return StatsManager.PULL_SUCCESS; + } + + private void registerMobileBytesTransferBackground() { + int tagId = StatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG; + PullAtomMetadata metadata = PullAtomMetadata.newBuilder() + .setAdditiveFields(new int[] {3, 4, 5, 6}) + .build(); + mStatsManager.registerPullAtomCallback( + tagId, + metadata, + (atomTag, data) -> pullMobileBytesTransferBackground(atomTag, data), + Executors.newSingleThreadExecutor() + ); + } + + private int pullMobileBytesTransferBackground(int atomTag, List<StatsEvent> pulledData) { + INetworkStatsService networkStatsService = getINetworkStatsService(); + if (networkStatsService == null) { + Slog.e(TAG, "NetworkStats Service is not available!"); + return StatsManager.PULL_SKIP; + } + long token = Binder.clearCallingIdentity(); + try { + BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class); + String[] ifaces = bs.getMobileIfaces(); + if (ifaces.length == 0) { + return StatsManager.PULL_SKIP; + } + NetworkStats stats = rollupNetworkStatsByFGBG( + networkStatsService.getDetailedUidStats(ifaces)); + addNetworkStats(atomTag, pulledData, stats, true); + } catch (RemoteException e) { + Slog.e(TAG, "Pulling netstats for mobile bytes w/ fg/bg has error", e); + return StatsManager.PULL_SKIP; + } finally { + Binder.restoreCallingIdentity(token); + } + return StatsManager.PULL_SUCCESS; + } + + private void registerBluetoothBytesTransfer() { + int tagId = StatsLog.BLUETOOTH_BYTES_TRANSFER; + PullAtomMetadata metadata = PullAtomMetadata.newBuilder() + .setAdditiveFields(new int[] {2, 3}) + .build(); + mStatsManager.registerPullAtomCallback( + tagId, + metadata, + (atomTag, data) -> pullBluetoothBytesTransfer(atomTag, data), + Executors.newSingleThreadExecutor() + ); + } + + /** + * Helper method to extract the Parcelable controller info from a + * SynchronousResultReceiver. + */ + private static <T extends Parcelable> T awaitControllerInfo( + @Nullable SynchronousResultReceiver receiver) { + if (receiver == null) { + return null; + } + + try { + final SynchronousResultReceiver.Result result = + receiver.awaitResult(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS); + if (result.bundle != null) { + // This is the final destination for the Bundle. + result.bundle.setDefusable(true); + + final T data = result.bundle.getParcelable(RESULT_RECEIVER_CONTROLLER_KEY); + if (data != null) { + return data; + } + } + Slog.e(TAG, "no controller energy info supplied for " + receiver.getName()); + } catch (TimeoutException e) { + Slog.w(TAG, "timeout reading " + receiver.getName() + " stats"); + } + return null; + } + + private synchronized BluetoothActivityEnergyInfo fetchBluetoothData() { + // TODO: Investigate whether the synchronized keyword is needed. + final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null) { + SynchronousResultReceiver bluetoothReceiver = new SynchronousResultReceiver( + "bluetooth"); + adapter.requestControllerActivityEnergyInfo(bluetoothReceiver); + return awaitControllerInfo(bluetoothReceiver); + } else { + Slog.e(TAG, "Failed to get bluetooth adapter!"); + return null; + } + } + + private int pullBluetoothBytesTransfer(int atomTag, List<StatsEvent> pulledData) { + BluetoothActivityEnergyInfo info = fetchBluetoothData(); + if (info == null || info.getUidTraffic() == null) { + return StatsManager.PULL_SKIP; + } + for (UidTraffic traffic : info.getUidTraffic()) { + StatsEvent e = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeInt(traffic.getUid()) + .writeLong(traffic.getRxBytes()) + .writeLong(traffic.getTxBytes()) + .build(); + pulledData.add(e); + } + return StatsManager.PULL_SUCCESS; + } + + private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader(); + private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats(); + + private void registerKernelWakelock() { + int tagId = StatsLog.KERNEL_WAKELOCK; + mStatsManager.registerPullAtomCallback( + tagId, + /* PullAtomMetadata */ null, + (atomTag, data) -> pullKernelWakelock(atomTag, data), + Executors.newSingleThreadExecutor() + ); + } + + private int pullKernelWakelock(int atomTag, List<StatsEvent> pulledData) { + final KernelWakelockStats wakelockStats = + mKernelWakelockReader.readKernelWakelockStats(mTmpWakelockStats); + for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) { + String name = ent.getKey(); + KernelWakelockStats.Entry kws = ent.getValue(); + StatsEvent e = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeString(name) + .writeInt(kws.mCount) + .writeInt(kws.mVersion) + .writeLong(kws.mTotalTime) + .build(); + pulledData.add(e); + } + return StatsManager.PULL_SUCCESS; + } + + private KernelCpuSpeedReader[] mKernelCpuSpeedReaders; + // Disables throttler on CPU time readers. + private KernelCpuUidUserSysTimeReader mCpuUidUserSysTimeReader = + new KernelCpuUidUserSysTimeReader(false); + private KernelCpuUidFreqTimeReader mCpuUidFreqTimeReader = + new KernelCpuUidFreqTimeReader(false); + private KernelCpuUidActiveTimeReader mCpuUidActiveTimeReader = + new KernelCpuUidActiveTimeReader(false); + private KernelCpuUidClusterTimeReader mCpuUidClusterTimeReader = + new KernelCpuUidClusterTimeReader(false); + + private void registerCpuTimePerFreq() { + int tagId = StatsLog.CPU_TIME_PER_FREQ; + PullAtomMetadata metadata = PullAtomMetadata.newBuilder() + .setAdditiveFields(new int[] {3}) + .build(); + mStatsManager.registerPullAtomCallback( + tagId, + metadata, + (atomTag, data) -> pullCpuTimePerFreq(atomTag, data), + Executors.newSingleThreadExecutor() + ); + } + + private int pullCpuTimePerFreq(int atomTag, List<StatsEvent> pulledData) { + for (int cluster = 0; cluster < mKernelCpuSpeedReaders.length; cluster++) { + long[] clusterTimeMs = mKernelCpuSpeedReaders[cluster].readAbsolute(); + if (clusterTimeMs != null) { + for (int speed = clusterTimeMs.length - 1; speed >= 0; --speed) { + StatsEvent e = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeInt(cluster) + .writeInt(speed) + .writeLong(clusterTimeMs[speed]) + .build(); + pulledData.add(e); + } + } + } + return StatsManager.PULL_SUCCESS; + } + + private void registerCpuTimePerUid() { + int tagId = StatsLog.CPU_TIME_PER_UID; + PullAtomMetadata metadata = PullAtomMetadata.newBuilder() + .setAdditiveFields(new int[] {2, 3}) + .build(); + mStatsManager.registerPullAtomCallback( + tagId, + metadata, + (atomTag, data) -> pullCpuTimePerUid(atomTag, data), + Executors.newSingleThreadExecutor() + ); + } + + private int pullCpuTimePerUid(int atomTag, List<StatsEvent> pulledData) { + mCpuUidUserSysTimeReader.readAbsolute((uid, timesUs) -> { + long userTimeUs = timesUs[0], systemTimeUs = timesUs[1]; + StatsEvent e = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeInt(uid) + .writeLong(userTimeUs) + .writeLong(systemTimeUs) + .build(); + pulledData.add(e); + }); + return StatsManager.PULL_SUCCESS; + } + + private void registerCpuTimePerUidFreq() { + // the throttling is 3sec, handled in + // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader + int tagId = StatsLog.CPU_TIME_PER_UID_FREQ; + PullAtomMetadata metadata = PullAtomMetadata.newBuilder() + .setAdditiveFields(new int[] {4}) + .build(); + mStatsManager.registerPullAtomCallback( + tagId, + metadata, + (atomTag, data) -> pullCpuTimeperUidFreq(atomTag, data), + Executors.newSingleThreadExecutor() + ); + } + + private int pullCpuTimeperUidFreq(int atomTag, List<StatsEvent> pulledData) { + mCpuUidFreqTimeReader.readAbsolute((uid, cpuFreqTimeMs) -> { + for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) { + if (cpuFreqTimeMs[freqIndex] != 0) { + StatsEvent e = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeInt(uid) + .writeInt(freqIndex) + .writeLong(cpuFreqTimeMs[freqIndex]) + .build(); + pulledData.add(e); + } + } + }); + return StatsManager.PULL_SUCCESS; + } + + private void registerCpuActiveTime() { + // the throttling is 3sec, handled in + // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader + int tagId = StatsLog.CPU_ACTIVE_TIME; + PullAtomMetadata metadata = PullAtomMetadata.newBuilder() + .setAdditiveFields(new int[] {2}) + .build(); + mStatsManager.registerPullAtomCallback( + tagId, + metadata, + (atomTag, data) -> pullCpuActiveTime(atomTag, data), + Executors.newSingleThreadExecutor() + ); + } + + private int pullCpuActiveTime(int atomTag, List<StatsEvent> pulledData) { + mCpuUidActiveTimeReader.readAbsolute((uid, cpuActiveTimesMs) -> { + StatsEvent e = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeInt(uid) + .writeLong(cpuActiveTimesMs) + .build(); + pulledData.add(e); + }); + return StatsManager.PULL_SUCCESS; + } + + private void registerCpuClusterTime() { + // the throttling is 3sec, handled in + // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader + int tagId = StatsLog.CPU_CLUSTER_TIME; + PullAtomMetadata metadata = PullAtomMetadata.newBuilder() + .setAdditiveFields(new int[] {3}) + .build(); + mStatsManager.registerPullAtomCallback( + tagId, + metadata, + (atomTag, data) -> pullCpuClusterTime(atomTag, data), + Executors.newSingleThreadExecutor() + ); + } + + private int pullCpuClusterTime(int atomTag, List<StatsEvent> pulledData) { + mCpuUidClusterTimeReader.readAbsolute((uid, cpuClusterTimesMs) -> { + for (int i = 0; i < cpuClusterTimesMs.length; i++) { + StatsEvent e = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeInt(uid) + .writeInt(i) + .writeLong(cpuClusterTimesMs[i]) + .build(); + pulledData.add(e); + } + }); + return StatsManager.PULL_SUCCESS; + } + + private void registerWifiActivityInfo() { + int tagId = StatsLog.WIFI_ACTIVITY_INFO; + mStatsManager.registerPullAtomCallback( + tagId, + null, // use default PullAtomMetadata values + (atomTag, data) -> pullWifiActivityInfo(atomTag, data), + BackgroundThread.getExecutor() + ); + } + + private WifiManager mWifiManager; + private TelephonyManager mTelephony; + + private int pullWifiActivityInfo(int atomTag, List<StatsEvent> pulledData) { + long token = Binder.clearCallingIdentity(); + try { + SynchronousResultReceiver wifiReceiver = new SynchronousResultReceiver("wifi"); + mWifiManager.getWifiActivityEnergyInfoAsync( + new Executor() { + @Override + public void execute(Runnable runnable) { + // run the listener on the binder thread, if it was run on the main + // thread it would deadlock since we would be waiting on ourselves + runnable.run(); + } + }, + info -> { + Bundle bundle = new Bundle(); + bundle.putParcelable(BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY, info); + wifiReceiver.send(0, bundle); + } + ); + final WifiActivityEnergyInfo wifiInfo = awaitControllerInfo(wifiReceiver); + if (wifiInfo == null) { + return StatsManager.PULL_SKIP; + } + StatsEvent e = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeLong(wifiInfo.getTimeSinceBootMillis()) + .writeInt(wifiInfo.getStackState()) + .writeLong(wifiInfo.getControllerTxDurationMillis()) + .writeLong(wifiInfo.getControllerRxDurationMillis()) + .writeLong(wifiInfo.getControllerIdleDurationMillis()) + .writeLong(wifiInfo.getControllerEnergyUsedMicroJoules()) + .build(); + pulledData.add(e); + } catch (RuntimeException e) { + Slog.e(TAG, "failed to getWifiActivityEnergyInfoAsync", e); + return StatsManager.PULL_SKIP; + } finally { + Binder.restoreCallingIdentity(token); + } + return StatsManager.PULL_SUCCESS; + } + + private void registerModemActivityInfo() { + int tagId = StatsLog.MODEM_ACTIVITY_INFO; + mStatsManager.registerPullAtomCallback( + tagId, + null, // use default PullAtomMetadata values + (atomTag, data) -> pullModemActivityInfo(atomTag, data), + BackgroundThread.getExecutor() + ); + } + + private int pullModemActivityInfo(int atomTag, List<StatsEvent> pulledData) { + long token = Binder.clearCallingIdentity(); + try { + SynchronousResultReceiver modemReceiver = new SynchronousResultReceiver("telephony"); + mTelephony.requestModemActivityInfo(modemReceiver); + final ModemActivityInfo modemInfo = awaitControllerInfo(modemReceiver); + if (modemInfo == null) { + return StatsManager.PULL_SKIP; + } + StatsEvent e = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeLong(modemInfo.getTimestamp()) + .writeLong(modemInfo.getSleepTimeMillis()) + .writeLong(modemInfo.getIdleTimeMillis()) + .writeLong(modemInfo.getTransmitPowerInfo().get(0).getTimeInMillis()) + .writeLong(modemInfo.getTransmitPowerInfo().get(1).getTimeInMillis()) + .writeLong(modemInfo.getTransmitPowerInfo().get(2).getTimeInMillis()) + .writeLong(modemInfo.getTransmitPowerInfo().get(3).getTimeInMillis()) + .writeLong(modemInfo.getTransmitPowerInfo().get(4).getTimeInMillis()) + .writeLong(modemInfo.getReceiveTimeMillis()) + .build(); + pulledData.add(e); + } finally { + Binder.restoreCallingIdentity(token); + } + return StatsManager.PULL_SUCCESS; + } + + private void registerBluetoothActivityInfo() { + int tagId = StatsLog.BLUETOOTH_ACTIVITY_INFO; + mStatsManager.registerPullAtomCallback( + tagId, + /* metadata */ null, + (atomTag, data) -> pullBluetoothActivityInfo(atomTag, data), + Executors.newSingleThreadExecutor() + ); + } + + private int pullBluetoothActivityInfo(int atomTag, List<StatsEvent> pulledData) { + BluetoothActivityEnergyInfo info = fetchBluetoothData(); + if (info == null) { + return StatsManager.PULL_SKIP; + } + StatsEvent e = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeLong(info.getTimeStamp()) + .writeInt(info.getBluetoothStackState()) + .writeLong(info.getControllerTxTimeMillis()) + .writeLong(info.getControllerRxTimeMillis()) + .writeLong(info.getControllerIdleTimeMillis()) + .writeLong(info.getControllerEnergyUsed()) + .build(); + pulledData.add(e); + return StatsManager.PULL_SUCCESS; + } + + private void registerSystemElapsedRealtime() { + // No op. + } + + private void pullSystemElapsedRealtime() { + // No op. + } + + private void registerSystemUptime() { + int tagId = StatsLog.SYSTEM_UPTIME; + mStatsManager.registerPullAtomCallback( + tagId, + null, // use default PullAtomMetadata values + (atomTag, data) -> pullSystemUptime(atomTag, data), + Executors.newSingleThreadExecutor() + ); + } + + private int pullSystemUptime(int atomTag, List<StatsEvent> pulledData) { + StatsEvent e = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeLong(SystemClock.elapsedRealtime()) + .build(); + pulledData.add(e); + return StatsManager.PULL_SUCCESS; + } + + private void registerRemainingBatteryCapacity() { + // No op. + } + + private void pullRemainingBatteryCapacity() { + // No op. + } + + private void registerFullBatteryCapacity() { + // No op. + } + + private void pullFullBatteryCapacity() { + // No op. + } + + private void registerBatteryVoltage() { + // No op. + } + + private void pullBatteryVoltage() { + // No op. + } + + private void registerBatteryLevel() { + // No op. + } + + private void pullBatteryLevel() { + // No op. + } + + private void registerBatteryCycleCount() { + // No op. + } + + private void pullBatteryCycleCount() { + // No op. + } + + private void registerProcessMemoryState() { + int tagId = StatsLog.PROCESS_MEMORY_STATE; + PullAtomMetadata metadata = PullAtomMetadata.newBuilder() + .setAdditiveFields(new int[] {4, 5, 6, 7, 8}) + .build(); + mStatsManager.registerPullAtomCallback( + tagId, + metadata, + (atomTag, data) -> pullProcessMemoryState(atomTag, data), + BackgroundThread.getExecutor() + ); + } + + private int pullProcessMemoryState(int atomTag, List<StatsEvent> pulledData) { + List<ProcessMemoryState> processMemoryStates = + LocalServices.getService(ActivityManagerInternal.class) + .getMemoryStateForProcesses(); + for (ProcessMemoryState processMemoryState : processMemoryStates) { + final MemoryStat memoryStat = readMemoryStatFromFilesystem(processMemoryState.uid, + processMemoryState.pid); + if (memoryStat == null) { + continue; + } + StatsEvent e = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeInt(processMemoryState.uid) + .writeString(processMemoryState.processName) + .writeInt(processMemoryState.oomScore) + .writeLong(memoryStat.pgfault) + .writeLong(memoryStat.pgmajfault) + .writeLong(memoryStat.rssInBytes) + .writeLong(memoryStat.cacheInBytes) + .writeLong(memoryStat.swapInBytes) + .writeLong(-1) // unused + .writeLong(-1) // unused + .writeInt(-1) // unused + .build(); + pulledData.add(e); + } + return StatsManager.PULL_SUCCESS; + } + + /** + * Which native processes to snapshot memory for. + * + * <p>Processes are matched by their cmdline in procfs. Example: cat /proc/pid/cmdline returns + * /system/bin/statsd for the stats daemon. + */ + private static final Set<String> MEMORY_INTERESTING_NATIVE_PROCESSES = Sets.newHashSet( + "/system/bin/statsd", // Stats daemon. + "/system/bin/surfaceflinger", + "/system/bin/apexd", // APEX daemon. + "/system/bin/audioserver", + "/system/bin/cameraserver", + "/system/bin/drmserver", + "/system/bin/healthd", + "/system/bin/incidentd", + "/system/bin/installd", + "/system/bin/lmkd", // Low memory killer daemon. + "/system/bin/logd", + "media.codec", + "media.extractor", + "media.metrics", + "/system/bin/mediadrmserver", + "/system/bin/mediaserver", + "/system/bin/performanced", + "/system/bin/tombstoned", + "/system/bin/traced", // Perfetto. + "/system/bin/traced_probes", // Perfetto. + "webview_zygote", + "zygote", + "zygote64"); + + /** + * Lowest available uid for apps. + * + * <p>Used to quickly discard memory snapshots of the zygote forks from native process + * measurements. + */ + private static final int MIN_APP_UID = 10_000; + + private static boolean isAppUid(int uid) { + return uid >= MIN_APP_UID; + } + + private void registerProcessMemoryHighWaterMark() { + int tagId = StatsLog.PROCESS_MEMORY_HIGH_WATER_MARK; + mStatsManager.registerPullAtomCallback( + tagId, + null, // use default PullAtomMetadata values + (atomTag, data) -> pullProcessMemoryHighWaterMark(atomTag, data), + BackgroundThread.getExecutor() + ); + } + + private int pullProcessMemoryHighWaterMark(int atomTag, List<StatsEvent> pulledData) { + List<ProcessMemoryState> managedProcessList = + LocalServices.getService(ActivityManagerInternal.class) + .getMemoryStateForProcesses(); + for (ProcessMemoryState managedProcess : managedProcessList) { + final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(managedProcess.pid); + if (snapshot == null) { + continue; + } + StatsEvent e = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeInt(managedProcess.uid) + .writeString(managedProcess.processName) + // RSS high-water mark in bytes. + .writeLong(snapshot.rssHighWaterMarkInKilobytes * 1024L) + .writeInt(snapshot.rssHighWaterMarkInKilobytes) + .build(); + pulledData.add(e); + } + forEachPid((pid, cmdLine) -> { + if (!MEMORY_INTERESTING_NATIVE_PROCESSES.contains(cmdLine)) { + return; + } + final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(pid); + if (snapshot == null) { + return; + } + // Sometimes we get here a process that is not included in the whitelist. It comes + // from forking the zygote for an app. We can ignore that sample because this process + // is collected by ProcessMemoryState. + if (isAppUid(snapshot.uid)) { + return; + } + StatsEvent e = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeInt(snapshot.uid) + .writeString(cmdLine) + // RSS high-water mark in bytes. + .writeLong(snapshot.rssHighWaterMarkInKilobytes * 1024L) + .writeInt(snapshot.rssHighWaterMarkInKilobytes) + .build(); + pulledData.add(e); + }); + // Invoke rss_hwm_reset binary to reset RSS HWM counters for all processes. + SystemProperties.set("sys.rss_hwm_reset.on", "1"); + return StatsManager.PULL_SUCCESS; + } + + private void registerProcessMemorySnapshot() { + int tagId = StatsLog.PROCESS_MEMORY_SNAPSHOT; + mStatsManager.registerPullAtomCallback( + tagId, + null, // use default PullAtomMetadata values + (atomTag, data) -> pullProcessMemorySnapshot(atomTag, data), + BackgroundThread.getExecutor() + ); + } + + private int pullProcessMemorySnapshot(int atomTag, List<StatsEvent> pulledData) { + List<ProcessMemoryState> managedProcessList = + LocalServices.getService(ActivityManagerInternal.class) + .getMemoryStateForProcesses(); + for (ProcessMemoryState managedProcess : managedProcessList) { + final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(managedProcess.pid); + if (snapshot == null) { + continue; + } + StatsEvent e = StatsEvent.newBuilder() + .writeInt(managedProcess.uid) + .writeString(managedProcess.processName) + .writeInt(managedProcess.pid) + .writeInt(managedProcess.oomScore) + .writeInt(snapshot.rssInKilobytes) + .writeInt(snapshot.anonRssInKilobytes) + .writeInt(snapshot.swapInKilobytes) + .writeInt(snapshot.anonRssInKilobytes + snapshot.swapInKilobytes) + .build(); + pulledData.add(e); + } + forEachPid((pid, cmdLine) -> { + if (!MEMORY_INTERESTING_NATIVE_PROCESSES.contains(cmdLine)) { + return; + } + final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(pid); + if (snapshot == null) { + return; + } + // Sometimes we get here a process that is not included in the whitelist. It comes + // from forking the zygote for an app. We can ignore that sample because this process + // is collected by ProcessMemoryState. + if (isAppUid(snapshot.uid)) { + return; + } + StatsEvent e = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeInt(snapshot.uid) + .writeString(cmdLine) + .writeInt(pid) + .writeInt(-1001) // Placeholder for native processes, OOM_SCORE_ADJ_MIN - 1. + .writeInt(snapshot.rssInKilobytes) + .writeInt(snapshot.anonRssInKilobytes) + .writeInt(snapshot.swapInKilobytes) + .writeInt(snapshot.anonRssInKilobytes + snapshot.swapInKilobytes) + .build(); + pulledData.add(e); + }); + return StatsManager.PULL_SUCCESS; + } + + private void registerSystemIonHeapSize() { + int tagId = StatsLog.SYSTEM_ION_HEAP_SIZE; + mStatsManager.registerPullAtomCallback( + tagId, + null, // use default PullAtomMetadata values + (atomTag, data) -> pullSystemIonHeapSize(atomTag, data), + BackgroundThread.getExecutor() + ); + } + + private int pullSystemIonHeapSize(int atomTag, List<StatsEvent> pulledData) { + final long systemIonHeapSizeInBytes = readSystemIonHeapSizeFromDebugfs(); + StatsEvent e = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeLong(systemIonHeapSizeInBytes) + .build(); + pulledData.add(e); + return StatsManager.PULL_SUCCESS; + } + + private void registerIonHeapSize() { + int tagId = StatsLog.ION_HEAP_SIZE; + mStatsManager.registerPullAtomCallback( + tagId, + /* PullAtomMetadata */ null, + (atomTag, data) -> pullIonHeapSize(atomTag, data), + Executors.newSingleThreadExecutor() + ); + } + + private int pullIonHeapSize(int atomTag, List<StatsEvent> pulledData) { + int ionHeapSizeInKilobytes = (int) getIonHeapsSizeKb(); + StatsEvent e = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeInt(ionHeapSizeInKilobytes) + .build(); + pulledData.add(e); + return StatsManager.PULL_SUCCESS; + } + + private void registerProcessSystemIonHeapSize() { + int tagId = StatsLog.PROCESS_SYSTEM_ION_HEAP_SIZE; + mStatsManager.registerPullAtomCallback( + tagId, + null, // use default PullAtomMetadata values + (atomTag, data) -> pullProcessSystemIonHeapSize(atomTag, data), + BackgroundThread.getExecutor() + ); + } + + private int pullProcessSystemIonHeapSize(int atomTag, List<StatsEvent> pulledData) { + List<IonAllocations> result = readProcessSystemIonHeapSizesFromDebugfs(); + for (IonAllocations allocations : result) { + StatsEvent e = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeInt(getUidForPid(allocations.pid)) + .writeString(readCmdlineFromProcfs(allocations.pid)) + .writeInt((int) (allocations.totalSizeInBytes / 1024)) + .writeInt(allocations.count) + .writeInt((int) (allocations.maxSizeInBytes / 1024)) + .build(); + pulledData.add(e); + } + return StatsManager.PULL_SUCCESS; + } + + private void registerTemperature() { + int tagId = StatsLog.TEMPERATURE; + mStatsManager.registerPullAtomCallback( + tagId, + null, // use default PullAtomMetadata values + (atomTag, data) -> pullTemperature(atomTag, data), + BackgroundThread.getExecutor() + ); + } + + private int pullTemperature(int atomTag, List<StatsEvent> pulledData) { + IThermalService thermalService = getIThermalService(); + if (thermalService == null) { + return StatsManager.PULL_SKIP; + } + final long callingToken = Binder.clearCallingIdentity(); + try { + List<Temperature> temperatures = thermalService.getCurrentTemperatures(); + for (Temperature temp : temperatures) { + StatsEvent e = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeInt(temp.getType()) + .writeString(temp.getName()) + .writeInt((int) (temp.getValue() * 10)) + .writeInt(temp.getStatus()) + .build(); + pulledData.add(e); + } + } catch (RemoteException e) { + // Should not happen. + Slog.e(TAG, "Disconnected from thermal service. Cannot pull temperatures."); + return StatsManager.PULL_SKIP; + } finally { + Binder.restoreCallingIdentity(callingToken); + } + return StatsManager.PULL_SUCCESS; + } + + private void registerCoolingDevice() { + int tagId = StatsLog.COOLING_DEVICE; + mStatsManager.registerPullAtomCallback( + tagId, + null, // use default PullAtomMetadata values + (atomTag, data) -> pullCooldownDevice(atomTag, data), + BackgroundThread.getExecutor() + ); + } + + private int pullCooldownDevice(int atomTag, List<StatsEvent> pulledData) { + IThermalService thermalService = getIThermalService(); + if (thermalService == null) { + return StatsManager.PULL_SKIP; + } + final long callingToken = Binder.clearCallingIdentity(); + try { + List<CoolingDevice> devices = thermalService.getCurrentCoolingDevices(); + for (CoolingDevice device : devices) { + StatsEvent e = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeInt(device.getType()) + .writeString(device.getName()) + .writeInt((int) (device.getValue())) + .build(); + pulledData.add(e); + } + } catch (RemoteException e) { + // Should not happen. + Slog.e(TAG, "Disconnected from thermal service. Cannot pull temperatures."); + return StatsManager.PULL_SKIP; + } finally { + Binder.restoreCallingIdentity(callingToken); + } + return StatsManager.PULL_SUCCESS; + } + + private void registerBinderCallsStats() { + int tagId = StatsLog.BINDER_CALLS; + PullAtomMetadata metadata = PullAtomMetadata.newBuilder() + .setAdditiveFields(new int[] {4, 5, 6, 8, 12}) + .build(); + mStatsManager.registerPullAtomCallback( + tagId, + metadata, + (atomTag, data) -> pullBinderCallsStats(atomTag, data), + BackgroundThread.getExecutor() + ); + } + + private int pullBinderCallsStats(int atomTag, List<StatsEvent> pulledData) { + BinderCallsStatsService.Internal binderStats = + LocalServices.getService(BinderCallsStatsService.Internal.class); + if (binderStats == null) { + Slog.e(TAG, "failed to get binderStats"); + return StatsManager.PULL_SKIP; + } + + List<ExportedCallStat> callStats = binderStats.getExportedCallStats(); + binderStats.reset(); + for (ExportedCallStat callStat : callStats) { + StatsEvent e = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeInt(callStat.workSourceUid) + .writeString(callStat.className) + .writeString(callStat.methodName) + .writeLong(callStat.callCount) + .writeLong(callStat.exceptionCount) + .writeLong(callStat.latencyMicros) + .writeLong(callStat.maxLatencyMicros) + .writeLong(callStat.cpuTimeMicros) + .writeLong(callStat.maxCpuTimeMicros) + .writeLong(callStat.maxReplySizeBytes) + .writeLong(callStat.maxRequestSizeBytes) + .writeLong(callStat.recordedCallCount) + .writeInt(callStat.screenInteractive ? 1 : 0) + .writeInt(callStat.callingUid) + .build(); + pulledData.add(e); + } + return StatsManager.PULL_SUCCESS; + } + + private void registerBinderCallsStatsExceptions() { + int tagId = StatsLog.BINDER_CALLS_EXCEPTIONS; + mStatsManager.registerPullAtomCallback( + tagId, + null, // use default PullAtomMetadata values + (atomTag, data) -> pullBinderCallsStatsExceptions(atomTag, data), + BackgroundThread.getExecutor() + ); + } + + private int pullBinderCallsStatsExceptions(int atomTag, List<StatsEvent> pulledData) { + BinderCallsStatsService.Internal binderStats = + LocalServices.getService(BinderCallsStatsService.Internal.class); + if (binderStats == null) { + Slog.e(TAG, "failed to get binderStats"); + return StatsManager.PULL_SKIP; + } + + ArrayMap<String, Integer> exceptionStats = binderStats.getExportedExceptionStats(); + // TODO: decouple binder calls exceptions with the rest of the binder calls data so that we + // can reset the exception stats. + for (Map.Entry<String, Integer> entry : exceptionStats.entrySet()) { + StatsEvent e = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeString(entry.getKey()) + .writeInt(entry.getValue()) + .build(); + pulledData.add(e); + } + return StatsManager.PULL_SUCCESS; + } + + private void registerLooperStats() { + // No op. + } + + private void pullLooperStats() { + // No op. + } + + private void registerDiskStats() { + // No op. + } + + private void pullDiskStats() { + // No op. + } + + private void registerDirectoryUsage() { + // No op. + } + + private void pullDirectoryUsage() { + // No op. + } + + private void registerAppSize() { + // No op. + } + + private void pullAppSize() { + // No op. + } + + private void registerCategorySize() { + // No op. + } + + private void pullCategorySize() { + // No op. + } + + private void registerNumFingerprintsEnrolled() { + // No op. + } + + private void pullNumFingerprintsEnrolled() { + // No op. + } + + private void registerNumFacesEnrolled() { + // No op. + } + + private void pullNumFacesEnrolled() { + // No op. + } + + private void registerProcStats() { + // No op. + } + + private void pullProcStats() { + // No op. + } + + private void registerProcStatsPkgProc() { + // No op. + } + + private void pullProcStatsPkgProc() { + // No op. + } + + private void registerDiskIO() { + // No op. + } + + private void pullDiskIO() { + // No op. + } + + private void registerPowerProfile() { + int tagId = StatsLog.POWER_PROFILE; + mStatsManager.registerPullAtomCallback( + tagId, + /* PullAtomMetadata */ null, + (atomTag, data) -> pullPowerProfile(atomTag, data), + Executors.newSingleThreadExecutor() + ); + } + + private int pullPowerProfile(int atomTag, List<StatsEvent> pulledData) { + PowerProfile powerProfile = new PowerProfile(mContext); + ProtoOutputStream proto = new ProtoOutputStream(); + powerProfile.dumpDebug(proto); + proto.flush(); + StatsEvent e = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeByteArray(proto.getBytes()) + .build(); + pulledData.add(e); + return StatsManager.PULL_SUCCESS; + } + + private void registerProcessCpuTime() { + // No op. + } + + private void pullProcessCpuTime() { + // No op. + } + + private void registerCpuTimePerThreadFreq() { + // No op. + } + + private void pullCpuTimePerThreadFreq() { + // No op. + } + + // TODO: move to top of file when all migrations are complete + private BatteryStatsHelper mBatteryStatsHelper = null; + private static final int MAX_BATTERY_STATS_HELPER_FREQUENCY_MS = 1000; + private long mBatteryStatsHelperTimestampMs = -MAX_BATTERY_STATS_HELPER_FREQUENCY_MS; + private static final long MILLI_AMP_HR_TO_NANO_AMP_SECS = 1_000_000L * 3600L; + + private BatteryStatsHelper getBatteryStatsHelper() { + if (mBatteryStatsHelper == null) { + final long callingToken = Binder.clearCallingIdentity(); + try { + // clearCallingIdentity required for BatteryStatsHelper.checkWifiOnly(). + mBatteryStatsHelper = new BatteryStatsHelper(mContext, false); + } finally { + Binder.restoreCallingIdentity(callingToken); + } + mBatteryStatsHelper.create((Bundle) null); + } + long currentTime = SystemClock.elapsedRealtime(); + if (currentTime - mBatteryStatsHelperTimestampMs >= MAX_BATTERY_STATS_HELPER_FREQUENCY_MS) { + // Load BatteryStats and do all the calculations. + mBatteryStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.USER_ALL); + // Calculations are done so we don't need to save the raw BatteryStats data in RAM. + mBatteryStatsHelper.clearStats(); + mBatteryStatsHelperTimestampMs = currentTime; + } + return mBatteryStatsHelper; + } + + private long milliAmpHrsToNanoAmpSecs(double mAh) { + return (long) (mAh * MILLI_AMP_HR_TO_NANO_AMP_SECS + 0.5); + } + + private void registerDeviceCalculatedPowerUse() { + int tagId = StatsLog.DEVICE_CALCULATED_POWER_USE; + mStatsManager.registerPullAtomCallback( + tagId, + null, // use default PullAtomMetadata values + (atomTag, data) -> pullDeviceCalculatedPowerUse(atomTag, data), + BackgroundThread.getExecutor() + ); + } + + private int pullDeviceCalculatedPowerUse(int atomTag, List<StatsEvent> pulledData) { + BatteryStatsHelper bsHelper = getBatteryStatsHelper(); + StatsEvent e = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeLong(milliAmpHrsToNanoAmpSecs(bsHelper.getComputedPower())) + .build(); + pulledData.add(e); + return StatsManager.PULL_SUCCESS; + } + + private void registerDeviceCalculatedPowerBlameUid() { + int tagId = StatsLog.DEVICE_CALCULATED_POWER_BLAME_UID; + mStatsManager.registerPullAtomCallback( + tagId, + null, // use default PullAtomMetadata values + (atomTag, data) -> pullDeviceCalculatedPowerBlameUid(atomTag, data), + BackgroundThread.getExecutor() + ); + } + + private int pullDeviceCalculatedPowerBlameUid(int atomTag, List<StatsEvent> pulledData) { + final List<BatterySipper> sippers = getBatteryStatsHelper().getUsageList(); + if (sippers == null) { + return StatsManager.PULL_SKIP; + } + + for (BatterySipper bs : sippers) { + if (bs.drainType != bs.drainType.APP) { + continue; + } + StatsEvent e = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeInt(bs.uidObj.getUid()) + .writeLong(milliAmpHrsToNanoAmpSecs(bs.totalPowerMah)) + .build(); + pulledData.add(e); + } + return StatsManager.PULL_SUCCESS; + } + + private void registerDeviceCalculatedPowerBlameOther() { + int tagId = StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER; + mStatsManager.registerPullAtomCallback( + tagId, + null, // use default PullAtomMetadata values + (atomTag, data) -> pullDeviceCalculatedPowerBlameOther(atomTag, data), + BackgroundThread.getExecutor() + ); + } + + private int pullDeviceCalculatedPowerBlameOther(int atomTag, List<StatsEvent> pulledData) { + final List<BatterySipper> sippers = getBatteryStatsHelper().getUsageList(); + if (sippers == null) { + return StatsManager.PULL_SKIP; + } + + for (BatterySipper bs : sippers) { + if (bs.drainType == bs.drainType.APP) { + continue; // This is a separate atom; see pullDeviceCalculatedPowerBlameUid(). + } + if (bs.drainType == bs.drainType.USER) { + continue; // This is not supported. We purposefully calculate over USER_ALL. + } + StatsEvent e = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeInt(bs.drainType.ordinal()) + .writeLong(milliAmpHrsToNanoAmpSecs(bs.totalPowerMah)) + .build(); + pulledData.add(e); + } + return StatsManager.PULL_SUCCESS; + } + + private void registerDebugElapsedClock() { + // No op. + } + + private void pullDebugElapsedClock() { + // No op. + } + + private void registerDebugFailingElapsedClock() { + // No op. + } + + private void pullDebugFailingElapsedClock() { + // No op. + } + + private void registerBuildInformation() { + int tagId = StatsLog.BUILD_INFORMATION; + mStatsManager.registerPullAtomCallback( + tagId, + null, // use default PullAtomMetadata values + (atomTag, data) -> pullBuildInformation(atomTag, data), + BackgroundThread.getExecutor() + ); + } + + private int pullBuildInformation(int atomTag, List<StatsEvent> pulledData) { + StatsEvent e = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeString(Build.FINGERPRINT) + .writeString(Build.BRAND) + .writeString(Build.PRODUCT) + .writeString(Build.DEVICE) + .writeString(Build.VERSION.RELEASE) + .writeString(Build.ID) + .writeString(Build.VERSION.INCREMENTAL) + .writeString(Build.TYPE) + .writeString(Build.TAGS) + .build(); + pulledData.add(e); + return StatsManager.PULL_SUCCESS; + } + + private void registerRoleHolder() { + // No op. + } + + private void pullRoleHolder() { + // No op. + } + + private void registerDangerousPermissionState() { + // No op. + } + + private void pullDangerousPermissionState() { + // No op. + } + + private void registerTimeZoneDataInfo() { + // No op. + } + + private void pullTimeZoneDataInfo() { + // No op. + } + + private void registerExternalStorageInfo() { + // No op. + } + + private void pullExternalStorageInfo() { + // No op. + } + + private void registerAppsOnExternalStorageInfo() { + // No op. + } + + private void pullAppsOnExternalStorageInfo() { + // No op. + } + + private void registerFaceSettings() { + // No op. + } + + private void pullRegisterFaceSettings() { + // No op. + } + + private void registerAppOps() { + // No op. + } + + private void pullAppOps() { + // No op. + } + + private void registerNotificationRemoteViews() { + // No op. + } + + private void pullNotificationRemoteViews() { + // No op. + } + + private void registerDangerousPermissionStateSampled() { + // No op. + } + + private void pullDangerousPermissionStateSampled() { + // No op. + } +} diff --git a/services/core/java/com/android/server/storage/StorageSessionController.java b/services/core/java/com/android/server/storage/StorageSessionController.java index f4fb93a0ce23..baef5d6ef2a0 100644 --- a/services/core/java/com/android/server/storage/StorageSessionController.java +++ b/services/core/java/com/android/server/storage/StorageSessionController.java @@ -40,9 +40,7 @@ import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; -import com.android.internal.util.Preconditions; -import java.io.FileDescriptor; import java.util.Objects; /** @@ -82,7 +80,7 @@ public final class StorageSessionController { * @throws ExternalStorageServiceException if the session fails to start * @throws IllegalStateException if a session has already been created for {@code vol} */ - public void onVolumeMount(FileDescriptor deviceFd, VolumeInfo vol) + public void onVolumeMount(ParcelFileDescriptor deviceFd, VolumeInfo vol) throws ExternalStorageServiceException { if (!shouldHandle(vol)) { return; @@ -102,8 +100,8 @@ public final class StorageSessionController { mConnections.put(userId, connection); } Slog.i(TAG, "Creating and starting session with id: " + sessionId); - connection.startSession(sessionId, new ParcelFileDescriptor(deviceFd), - vol.getPath().getPath(), vol.getInternalPath().getPath()); + connection.startSession(sessionId, deviceFd, vol.getPath().getPath(), + vol.getInternalPath().getPath()); } } @@ -185,7 +183,7 @@ public final class StorageSessionController { * This call removes all sessions for the user that is being stopped; * this will make sure that we don't rebind to the service needlessly. */ - public void onUserStopping(int userId) throws ExternalStorageServiceException { + public void onUserStopping(int userId) { if (!shouldHandle(null)) { return; } diff --git a/services/core/java/com/android/server/storage/StorageUserConnection.java b/services/core/java/com/android/server/storage/StorageUserConnection.java index c02ded83586a..dd18f4e5ab17 100644 --- a/services/core/java/com/android/server/storage/StorageUserConnection.java +++ b/services/core/java/com/android/server/storage/StorageUserConnection.java @@ -60,7 +60,8 @@ import java.util.concurrent.TimeoutException; */ public final class StorageUserConnection { private static final String TAG = "StorageUserConnection"; - private static final int REMOTE_TIMEOUT_SECONDS = 15; + + public static final int REMOTE_TIMEOUT_SECONDS = 5; private final Object mLock = new Object(); private final Context mContext; diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java index c96479543b3a..468b806d6dce 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java @@ -61,10 +61,10 @@ public interface TimeDetectorStrategy { /** Acquire a suitable wake lock. Must be followed by {@link #releaseWakeLock()} */ void acquireWakeLock(); - /** Returns the elapsedRealtimeMillis clock value. The WakeLock must be held. */ + /** Returns the elapsedRealtimeMillis clock value. */ long elapsedRealtimeMillis(); - /** Returns the system clock value. The WakeLock must be held. */ + /** Returns the system clock value. */ long systemClockMillis(); /** Sets the device system clock. The WakeLock must be held. */ diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java index 9b89d9437fc3..19484db149b1 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java @@ -88,13 +88,11 @@ public final class TimeDetectorStrategyCallbackImpl implements TimeDetectorStrat @Override public long elapsedRealtimeMillis() { - checkWakeLockHeld(); return SystemClock.elapsedRealtime(); } @Override public long systemClockMillis() { - checkWakeLockHeld(); return System.currentTimeMillis(); } diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 5b58199aec4f..eb7c5caa62aa 100755 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -127,6 +127,9 @@ public final class TvInputManagerService extends SystemService { // A map from user id to UserState. private final SparseArray<UserState> mUserStates = new SparseArray<>(); + // A map from session id to session state saved in userstate + private final Map<String, SessionState> mSessionIdToSessionStateMap = new HashMap<>(); + private final WatchLogHandler mWatchLogHandler; public TvInputManagerService(Context context) { @@ -632,7 +635,8 @@ public final class TvInputManagerService extends SystemService { UserState userState = getOrCreateUserStateLocked(userId); SessionState sessionState = userState.sessionStateMap.get(sessionToken); if (DEBUG) { - Slog.d(TAG, "createSessionInternalLocked(inputId=" + sessionState.inputId + ")"); + Slog.d(TAG, "createSessionInternalLocked(inputId=" + + sessionState.inputId + ", sessionId=" + sessionState.sessionId + ")"); } InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString()); @@ -643,9 +647,11 @@ public final class TvInputManagerService extends SystemService { // Create a session. When failed, send a null token immediately. try { if (sessionState.isRecordingSession) { - service.createRecordingSession(callback, sessionState.inputId); + service.createRecordingSession( + callback, sessionState.inputId, sessionState.sessionId); } else { - service.createSession(channels[1], callback, sessionState.inputId); + service.createSession( + channels[1], callback, sessionState.inputId, sessionState.sessionId); } } catch (RemoteException e) { Slog.e(TAG, "error in createSession", e); @@ -715,6 +721,8 @@ public final class TvInputManagerService extends SystemService { } } + mSessionIdToSessionStateMap.remove(sessionState.sessionId); + ServiceState serviceState = userState.serviceStateMap.get(sessionState.componentName); if (serviceState != null) { serviceState.sessionTokens.remove(sessionToken); @@ -1156,9 +1164,11 @@ public final class TvInputManagerService extends SystemService { public void createSession(final ITvInputClient client, final String inputId, boolean isRecordingSession, int seq, int userId) { final int callingUid = Binder.getCallingUid(); - final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + final int callingPid = Binder.getCallingPid(); + final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId, "createSession"); final long identity = Binder.clearCallingIdentity(); + StringBuilder sessionId = new StringBuilder(); try { synchronized (mLock) { if (userId != mCurrentUserId && !isRecordingSession) { @@ -1187,15 +1197,21 @@ public final class TvInputManagerService extends SystemService { return; } + // Create a unique session id with pid, uid and resolved user id + sessionId.append(callingUid).append(callingPid).append(resolvedUserId); + // Create a new session token and a session state. IBinder sessionToken = new Binder(); SessionState sessionState = new SessionState(sessionToken, info.getId(), info.getComponent(), isRecordingSession, client, seq, callingUid, - resolvedUserId); + callingPid, resolvedUserId, sessionId.toString()); // Add them to the global session state map of the current user. userState.sessionStateMap.put(sessionToken, sessionState); + // Map the session id to the sessionStateMap in the user state + mSessionIdToSessionStateMap.put(sessionId.toString(), sessionState); + // Also, add them to the session state map of the current service. serviceState.sessionTokens.add(sessionToken); @@ -2003,6 +2019,43 @@ public final class TvInputManagerService extends SystemService { } @Override + public int getClientPid(String sessionId) { + ensureTunerResourceAccessPermission(); + final long identity = Binder.clearCallingIdentity(); + + int clientPid = TvInputManager.UNKNOWN_CLIENT_PID; + try { + synchronized (mLock) { + try { + clientPid = getClientPidLocked(sessionId); + } catch (ClientPidNotFoundException e) { + Slog.e(TAG, "error in getClientPid", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + return clientPid; + } + + private int getClientPidLocked(String sessionId) + throws IllegalStateException { + if (mSessionIdToSessionStateMap.get(sessionId) == null) { + throw new IllegalStateException("Client Pid not found with sessionId " + + sessionId); + } + return mSessionIdToSessionStateMap.get(sessionId).callingPid; + } + + private void ensureTunerResourceAccessPermission() { + if (mContext.checkCallingPermission( + android.Manifest.permission.TUNER_RESOURCE_ACCESS) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires TUNER_RESOURCE_ACCESS permission"); + } + } + + @Override @SuppressWarnings("resource") protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) { final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); @@ -2094,9 +2147,11 @@ public final class TvInputManagerService extends SystemService { pw.increaseIndent(); pw.println("inputId: " + session.inputId); + pw.println("sessionId: " + session.sessionId); pw.println("client: " + session.client); pw.println("seq: " + session.seq); pw.println("callingUid: " + session.callingUid); + pw.println("callingPid: " + session.callingPid); pw.println("userId: " + session.userId); pw.println("sessionToken: " + session.sessionToken); pw.println("session: " + session.session); @@ -2226,11 +2281,13 @@ public final class TvInputManagerService extends SystemService { private final class SessionState implements IBinder.DeathRecipient { private final String inputId; + private final String sessionId; private final ComponentName componentName; private final boolean isRecordingSession; private final ITvInputClient client; private final int seq; private final int callingUid; + private final int callingPid; private final int userId; private final IBinder sessionToken; private ITvInputSession session; @@ -2240,7 +2297,7 @@ public final class TvInputManagerService extends SystemService { private SessionState(IBinder sessionToken, String inputId, ComponentName componentName, boolean isRecordingSession, ITvInputClient client, int seq, int callingUid, - int userId) { + int callingPid, int userId, String sessionId) { this.sessionToken = sessionToken; this.inputId = inputId; this.componentName = componentName; @@ -2248,7 +2305,9 @@ public final class TvInputManagerService extends SystemService { this.client = client; this.seq = seq; this.callingUid = callingUid; + this.callingPid = callingPid; this.userId = userId; + this.sessionId = sessionId; } @Override @@ -2962,4 +3021,10 @@ public final class TvInputManagerService extends SystemService { super(name); } } + + private static class ClientPidNotFoundException extends IllegalArgumentException { + public ClientPidNotFoundException(String name) { + super(name); + } + } } diff --git a/services/core/java/com/android/server/utils/quota/QuotaTracker.java b/services/core/java/com/android/server/utils/quota/QuotaTracker.java index ef1f42647e30..a8cf9f6c0ec4 100644 --- a/services/core/java/com/android/server/utils/quota/QuotaTracker.java +++ b/services/core/java/com/android/server/utils/quota/QuotaTracker.java @@ -172,6 +172,16 @@ abstract class QuotaTracker { // Exposed API to users. + /** Remove all saved events from the tracker. */ + public void clear() { + synchronized (mLock) { + mInQuotaAlarmListener.clearLocked(); + mFreeQuota.clear(); + + dropEverythingLocked(); + } + } + /** * @return true if the UPTC is within quota, false otherwise. * @throws IllegalStateException if given categorizer returns a Category that's not recognized. @@ -245,10 +255,7 @@ abstract class QuotaTracker { mIsEnabled = enable; if (!mIsEnabled) { - mInQuotaAlarmListener.clearLocked(); - mFreeQuota.clear(); - - dropEverythingLocked(); + clear(); } } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 26d76a8d6e28..3f3408f4d202 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1633,12 +1633,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A requestedVrComponent = (aInfo.requestedVrComponent == null) ? null : ComponentName.unflattenFromString(aInfo.requestedVrComponent); - lockTaskLaunchMode = aInfo.lockTaskLaunchMode; - if (info.applicationInfo.isPrivilegedApp() - && (lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_ALWAYS - || lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_NEVER)) { - lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT; - } + lockTaskLaunchMode = getLockTaskLaunchMode(aInfo, options); if (options != null) { pendingOptions = options; @@ -1646,13 +1641,25 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (usageReport != null) { appTimeTracker = new AppTimeTracker(usageReport); } - final boolean useLockTask = pendingOptions.getLockTaskMode(); + // Gets launch display id from options. It returns INVALID_DISPLAY if not set. + mHandoverLaunchDisplayId = options.getLaunchDisplayId(); + } + } + + static int getLockTaskLaunchMode(ActivityInfo aInfo, @Nullable ActivityOptions options) { + int lockTaskLaunchMode = aInfo.lockTaskLaunchMode; + if (aInfo.applicationInfo.isPrivilegedApp() + && (lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_ALWAYS + || lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_NEVER)) { + lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT; + } + if (options != null) { + final boolean useLockTask = options.getLockTaskMode(); if (useLockTask && lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_DEFAULT) { lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED; } - // Gets launch display id from options. It returns INVALID_DISPLAY if not set. - mHandoverLaunchDisplayId = options.getLaunchDisplayId(); } + return lockTaskLaunchMode; } @Override @@ -2130,8 +2137,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A (intent == null || (intent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0); } + @Override boolean isFocusable() { - return mRootWindowContainer.isFocusable(this, isAlwaysFocusable()); + return super.isFocusable() + && (getWindowConfiguration().canReceiveKeys() || isAlwaysFocusable()); } boolean isResizeable() { @@ -2482,7 +2491,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // We are finishing the top focused activity and its stack has nothing to be focused so // the next focusable stack should be focused. if (mayAdjustTop - && (stack.topRunningActivity() == null || !stack.isFocusable())) { + && (stack.topRunningActivity() == null || !stack.isTopActivityFocusable())) { if (shouldAdjustGlobalFocus) { // Move the entire hierarchy to top with updating global top resumed activity // and focused application if needed. @@ -5934,7 +5943,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mAnimationBoundsLayer = createAnimationBoundsLayer(t); // Crop to stack bounds. - t.setWindowCrop(mAnimationBoundsLayer, mTmpRect); + if (!WindowManagerService.sHierarchicalAnimations) { + // For Hierarchical animation, we don't need to set window crop since the leash + // surface size has already same as the animating container. + t.setWindowCrop(mAnimationBoundsLayer, mTmpRect); + } t.setLayer(mAnimationBoundsLayer, layer); // Reparent leash to animation bounds layer. @@ -5982,9 +5995,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return; } clearThumbnail(); + final Transaction transaction = getAnimatingContainer().getPendingTransaction(); mThumbnail = new WindowContainerThumbnail(mWmService.mSurfaceFactory, - getPendingTransaction(), this, thumbnailHeader); - mThumbnail.startAnimation(getPendingTransaction(), loadThumbnailAnimation(thumbnailHeader)); + transaction, getAnimatingContainer(), thumbnailHeader); + mThumbnail.startAnimation(transaction, loadThumbnailAnimation(thumbnailHeader)); } /** @@ -6011,13 +6025,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (thumbnail == null) { return; } + final Transaction transaction = getAnimatingContainer().getPendingTransaction(); mThumbnail = new WindowContainerThumbnail(mWmService.mSurfaceFactory, - getPendingTransaction(), this, thumbnail); + transaction, getAnimatingContainer(), thumbnail); final Animation animation = getDisplayContent().mAppTransition.createCrossProfileAppsThumbnailAnimationLocked( win.getFrameLw()); - mThumbnail.startAnimation(getPendingTransaction(), animation, new Point(frame.left, - frame.top)); + mThumbnail.startAnimation(transaction, animation, new Point(frame.left, frame.top)); } private Animation loadThumbnailAnimation(GraphicBuffer thumbnailHeader) { @@ -6349,6 +6363,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * aspect ratio. */ boolean shouldUseSizeCompatMode() { + if (inMultiWindowMode() || getWindowConfiguration().hasWindowDecorCaption()) { + final ActivityRecord root = task != null ? task.getRootActivity() : null; + if (root != null && root != this && !root.shouldUseSizeCompatMode()) { + // If the root activity doesn't use size compatibility mode, the activities above + // are forced to be the same for consistent visual appearance. + return false; + } + } return !isResizeable() && (info.isFixedOrientation() || info.hasFixedAspectRatio()) // The configuration of non-standard type should be enforced by system. && isActivityTypeStandard() diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index 9fd3ea4fc090..f5fba8e85d64 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -161,9 +161,9 @@ import android.util.proto.ProtoOutputStream; import android.view.Display; import android.view.DisplayCutout; import android.view.DisplayInfo; +import android.view.ITaskOrganizer; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; -import android.view.ITaskOrganizer; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -1248,13 +1248,20 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn } } + @Override boolean isFocusable() { + return super.isFocusable() && !(inSplitScreenPrimaryWindowingMode() + && mRootWindowContainer.mIsDockMinimized); + } + + boolean isTopActivityFocusable() { final ActivityRecord r = topRunningActivity(); - return mRootWindowContainer.isFocusable(this, r != null && r.isFocusable()); + return r != null ? r.isFocusable() + : (isFocusable() && getWindowConfiguration().canReceiveKeys()); } boolean isFocusableAndVisible() { - return isFocusable() && shouldBeVisible(null /* starting */); + return isTopActivityFocusable() && shouldBeVisible(null /* starting */); } @Override diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java index df97caa76d2d..d61d29d1084e 100644 --- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java +++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java @@ -52,6 +52,7 @@ import android.os.UserHandle; import android.os.UserManager; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.app.BlockedAppActivity; import com.android.internal.app.HarmfulAppWarningActivity; import com.android.internal.app.SuspendedAppActivity; import com.android.internal.app.UnlaunchableAppActivity; @@ -166,6 +167,9 @@ class ActivityStartInterceptor { // no user action can undo this. return true; } + if (interceptLockTaskModeViolationPackageIfNeeded()) { + return true; + } if (interceptHarmfulAppIfNeeded()) { // If the app has a "harmful app" warning associated with it, we should ask to uninstall // before issuing the work challenge. @@ -262,6 +266,25 @@ class ActivityStartInterceptor { return true; } + private boolean interceptLockTaskModeViolationPackageIfNeeded() { + if (mAInfo == null || mAInfo.applicationInfo == null) { + return false; + } + LockTaskController controller = mService.getLockTaskController(); + String packageName = mAInfo.applicationInfo.packageName; + int lockTaskLaunchMode = ActivityRecord.getLockTaskLaunchMode(mAInfo, mActivityOptions); + if (controller.isActivityAllowed(mUserId, packageName, lockTaskLaunchMode)) { + return false; + } + mIntent = BlockedAppActivity.createIntent(mUserId, mAInfo.applicationInfo.packageName); + mCallingPid = mRealCallingPid; + mCallingUid = mRealCallingUid; + mResolvedType = null; + mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, mUserId, 0, mRealCallingUid); + mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, null /*profilerInfo*/); + return true; + } + private boolean interceptWorkProfileChallengeIfNeeded() { final Intent interceptingIntent = interceptWithConfirmCredentialsIfNeeded(mAInfo, mUserId); if (interceptingIntent == null) { diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 61ba15c0013b..6587226b14d7 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1569,7 +1569,7 @@ class ActivityStarter { if (mDoResume) { final ActivityRecord topTaskActivity = mStartActivity.getTask().topRunningActivityLocked(); - if (!mTargetStack.isFocusable() + if (!mTargetStack.isTopActivityFocusable() || (topTaskActivity != null && topTaskActivity.isTaskOverlay() && mStartActivity != topTaskActivity)) { // If the activity is not focusable, we can't resume it, but still would like to @@ -1588,7 +1588,7 @@ class ActivityStarter { // will not update the focused stack. If starting the new activity now allows the // task stack to be focusable, then ensure that we now update the focused stack // accordingly. - if (mTargetStack.isFocusable() + if (mTargetStack.isTopActivityFocusable() && !mRootWindowContainer.isTopDisplayFocusedStack(mTargetStack)) { mTargetStack.moveToFront("startActivityInner"); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index ded603c9fd77..e308f6b8f3ce 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -28,7 +28,7 @@ import static android.Manifest.permission.REMOVE_TASKS; import static android.Manifest.permission.START_TASKS_FROM_RECENTS; import static android.Manifest.permission.STOP_APP_SWITCHES; import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; -import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; +import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.ActivityTaskManager.RESIZE_MODE_PRESERVE_WINDOW; import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; @@ -248,7 +248,6 @@ import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.KeyguardDismissCallback; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastPrintWriter; -import com.android.internal.util.Preconditions; import com.android.internal.util.function.pooled.PooledConsumer; import com.android.internal.util.function.pooled.PooledFunction; import com.android.internal.util.function.pooled.PooledLambda; @@ -345,6 +344,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { /** This activity is being relaunched due to a free-resize operation. */ public static final int RELAUNCH_REASON_FREE_RESIZE = 2; + /** Flag indicating that an applied transaction may have effected lifecycle */ + private static final int TRANSACT_EFFECTS_CLIENT_CONFIG = 1; + private static final int TRANSACT_EFFECTS_LIFECYCLE = 1 << 1; + Context mContext; /** @@ -1436,7 +1439,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { int handleIncomingUser(int callingPid, int callingUid, int userId, String name) { return mAmInternal.handleIncomingUser(callingPid, callingUid, userId, false /* allowAll */, - ALLOW_FULL_ONLY, name, null /* callerPackage */); + ALLOW_NON_FULL, name, null /* callerPackage */); } @Override @@ -3300,7 +3303,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } - private void sanitizeAndApplyConfigChange(ConfigurationContainer container, + private int sanitizeAndApplyChange(ConfigurationContainer container, WindowContainerTransaction.Change change) { if (!(container instanceof Task || container instanceof ActivityStack)) { throw new RuntimeException("Invalid token in task transaction"); @@ -3312,12 +3315,22 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { configMask &= ActivityInfo.CONFIG_WINDOW_CONFIGURATION | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; windowMask &= WindowConfiguration.WINDOW_CONFIG_BOUNDS; - Configuration c = new Configuration(container.getRequestedOverrideConfiguration()); - c.setTo(change.getConfiguration(), configMask, windowMask); - container.onRequestedOverrideConfigurationChanged(c); - // TODO(b/145675353): remove the following once we could apply new bounds to the - // pinned stack together with its children. - resizePinnedStackIfNeeded(container, configMask, windowMask, c); + int effects = 0; + if (configMask != 0) { + Configuration c = new Configuration(container.getRequestedOverrideConfiguration()); + c.setTo(change.getConfiguration(), configMask, windowMask); + container.onRequestedOverrideConfigurationChanged(c); + // TODO(b/145675353): remove the following once we could apply new bounds to the + // pinned stack together with its children. + resizePinnedStackIfNeeded(container, configMask, windowMask, c); + effects |= TRANSACT_EFFECTS_CLIENT_CONFIG; + } + if ((change.getChangeMask() & WindowContainerTransaction.Change.CHANGE_FOCUSABLE) != 0) { + if (container.setFocusable(change.getFocusable())) { + effects |= TRANSACT_EFFECTS_LIFECYCLE; + } + } + return effects; } private void resizePinnedStackIfNeeded(ConfigurationContainer container, int configMask, @@ -3334,9 +3347,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } - private void applyWindowContainerChange(ConfigurationContainer cc, + private int applyWindowContainerChange(ConfigurationContainer cc, WindowContainerTransaction.Change c) { - sanitizeAndApplyConfigChange(cc, c); + int effects = sanitizeAndApplyChange(cc, c); Rect enterPipBounds = c.getEnterPipBounds(); if (enterPipBounds != null) { @@ -3344,25 +3357,58 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { mStackSupervisor.updatePictureInPictureMode(tr, enterPipBounds, true); } + return effects; } @Override public void applyContainerTransaction(WindowContainerTransaction t) { mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "applyContainerTransaction()"); + if (t == null) { + return; + } long ident = Binder.clearCallingIdentity(); try { - if (t == null) { - return; - } synchronized (mGlobalLock) { - Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries = - t.getChanges().entrySet().iterator(); - while (entries.hasNext()) { - final Map.Entry<IBinder, WindowContainerTransaction.Change> entry = - entries.next(); - final ConfigurationContainer cc = ConfigurationContainer.RemoteToken.fromBinder( - entry.getKey()).getContainer(); - applyWindowContainerChange(cc, entry.getValue()); + int effects = 0; + deferWindowLayout(); + try { + ArraySet<WindowContainer> haveConfigChanges = new ArraySet<>(); + Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries = + t.getChanges().entrySet().iterator(); + while (entries.hasNext()) { + final Map.Entry<IBinder, WindowContainerTransaction.Change> entry = + entries.next(); + final ConfigurationContainer cc = + ConfigurationContainer.RemoteToken.fromBinder( + entry.getKey()).getContainer(); + int containerEffect = applyWindowContainerChange(cc, entry.getValue()); + effects |= containerEffect; + // Lifecycle changes will trigger ensureConfig for everything. + if ((effects & TRANSACT_EFFECTS_LIFECYCLE) == 0 + && (containerEffect & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) { + if (cc instanceof WindowContainer) { + haveConfigChanges.add((WindowContainer) cc); + } + } + } + if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) { + // Already calls ensureActivityConfig + mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS); + } else if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) { + final PooledConsumer f = PooledLambda.obtainConsumer( + ActivityRecord::ensureActivityConfiguration, + PooledLambda.__(ActivityRecord.class), 0, + false /* preserveWindow */); + try { + for (int i = haveConfigChanges.size() - 1; i >= 0; --i) { + haveConfigChanges.valueAt(i).forAllActivities(f); + } + } finally { + f.recycle(); + } + } + } finally { + continueWindowLayout(); } } } finally { diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index d0310f1a7607..1e60ce83b5fb 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainer.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java @@ -624,6 +624,21 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { return mFullConfiguration.windowConfiguration.isAlwaysOnTop(); } + /** + * Returns {@code true} if this container is focusable. Generally, if a parent is not focusable, + * this will not be focusable either. + */ + boolean isFocusable() { + // TODO(split): Move this to WindowContainer once Split-screen is based on a WindowContainer + // like DisplayArea vs. TaskTiles. + ConfigurationContainer parent = getParent(); + return parent == null || parent.isFocusable(); + } + + boolean setFocusable(boolean focusable) { + return false; + } + boolean hasChild() { return getChildCount() > 0; } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index ba9d757d979f..908c4f1ddd66 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -5915,7 +5915,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo final ActivityRecord resumedActivity = stack.getResumedActivity(); if (resumedActivity != null && (stack.getVisibility(resuming) != STACK_VISIBILITY_VISIBLE - || !stack.isFocusable())) { + || !stack.isTopActivityFocusable())) { if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack + " mResumedActivity=" + resumedActivity); someActivityPaused |= stack.startPausingLocked(userLeaving, false /* uiSleeping*/, @@ -6238,7 +6238,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo for (int i = getStackCount() - 1; i >= 0; --i) { final ActivityStack stack = getStackAt(i); // Only consider focusable stacks other than the current focused one. - if (stack == focusedStack || !stack.isFocusable()) { + if (stack == focusedStack || !stack.isTopActivityFocusable()) { continue; } topRunning = stack.topRunningActivity(); diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java index c09834f2b3c1..9d985d7048e5 100644 --- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java +++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java @@ -75,7 +75,8 @@ class EnsureActivitiesVisibleHelper { // activities are actually behind other fullscreen activities, but still required // to be visible (such as performing Recents animation). final boolean resumeTopActivity = mTop != null && !mTop.mLaunchTaskBehind - && mContiner.isFocusable() && mContiner.isInStackLocked(starting) == null; + && mContiner.isTopActivityFocusable() + && mContiner.isInStackLocked(starting) == null; final PooledConsumer f = PooledLambda.obtainConsumer( EnsureActivitiesVisibleHelper::setActivityVisibilityState, this, diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java index 02413bb48518..33b0453a25ee 100644 --- a/services/core/java/com/android/server/wm/LockTaskController.java +++ b/services/core/java/com/android/server/wm/LockTaskController.java @@ -23,6 +23,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.Context.DEVICE_POLICY_SERVICE; import static android.content.Context.STATUS_BAR_SERVICE; import static android.content.Intent.ACTION_CALL_EMERGENCY; +import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS; +import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER; import static android.os.UserHandle.USER_ALL; import static android.os.UserHandle.USER_CURRENT; import static android.telecom.TelecomManager.EMERGENCY_DIALER_COMPONENT; @@ -339,6 +341,20 @@ public class LockTaskController { & DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD) != 0; } + boolean isActivityAllowed(int userId, String packageName, int lockTaskLaunchMode) { + if (mLockTaskModeState != LOCK_TASK_MODE_LOCKED) { + return true; + } + switch (lockTaskLaunchMode) { + case LOCK_TASK_LAUNCH_MODE_ALWAYS: + return true; + case LOCK_TASK_LAUNCH_MODE_NEVER: + return false; + default: + } + return isPackageWhitelisted(userId, packageName); + } + private boolean isEmergencyCallTask(Task task) { final Intent intent = task.intent; if (intent == null) { diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java index e0a7b18f40c0..2cb7d5a23647 100644 --- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java +++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java @@ -33,6 +33,27 @@ class RefreshRatePolicy { private final HighRefreshRateBlacklist mHighRefreshRateBlacklist; private final WindowManagerService mWmService; + /** + * The following constants represent priority of the window. SF uses this information when + * deciding which window has a priority when deciding about the refresh rate of the screen. + * Priority 0 is considered the highest priority. -1 means that the priority is unset. + */ + static final int LAYER_PRIORITY_UNSET = -1; + /** Windows that are in focus and voted for the preferred mode ID have the highest priority. */ + static final int LAYER_PRIORITY_FOCUSED_WITH_MODE = 0; + /** + * This is a default priority for all windows that are in focus, but have not requested a + * specific mode ID. + */ + static final int LAYER_PRIORITY_FOCUSED_WITHOUT_MODE = 1; + /** + * Windows that are not in focus, but voted for a specific mode ID should be + * acknowledged by SF. For example, there are two applications in a split screen. + * One voted for a given mode ID, and the second one doesn't care. Even though the + * second one might be in focus, we can honor the mode ID of the first one. + */ + static final int LAYER_PRIORITY_NOT_FOCUSED_WITH_MODE = 2; + RefreshRatePolicy(WindowManagerService wmService, DisplayInfo displayInfo, HighRefreshRateBlacklist blacklist) { mLowRefreshRateId = findLowRefreshRateModeId(displayInfo); @@ -92,4 +113,28 @@ class RefreshRatePolicy { } return 0; } + + /** + * Calculate the priority based on whether the window is in focus and whether the application + * voted for a specific refresh rate. + * + * TODO(b/144307188): This is a very basic algorithm version. Explore other signals that might + * be useful in edge cases when we are deciding which layer should get priority when deciding + * about the refresh rate. + */ + int calculatePriority(WindowState w) { + boolean isFocused = w.isFocused(); + int preferredModeId = getPreferredModeId(w); + + if (!isFocused && preferredModeId > 0) { + return LAYER_PRIORITY_NOT_FOCUSED_WITH_MODE; + } + if (isFocused && preferredModeId == 0) { + return LAYER_PRIORITY_FOCUSED_WITHOUT_MODE; + } + if (isFocused && preferredModeId > 0) { + return LAYER_PRIORITY_FOCUSED_WITH_MODE; + } + return LAYER_PRIORITY_UNSET; + } } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index c3e815d10dda..2f726e9999eb 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -1860,14 +1860,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> return null; } - boolean isFocusable(ConfigurationContainer container, boolean alwaysFocusable) { - if (container.inSplitScreenPrimaryWindowingMode() && mIsDockMinimized) { - return false; - } - - return container.getWindowConfiguration().canReceiveKeys() || alwaysFocusable; - } - boolean isTopDisplayFocusedStack(ActivityStack stack) { return stack != null && stack == getTopDisplayFocusedStack(); } diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java index d36a5d4eb362..38a7000803bd 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java @@ -53,7 +53,7 @@ class TaskSnapshotPersister { private static final String SNAPSHOTS_DIRNAME = "snapshots"; private static final String REDUCED_POSTFIX = "_reduced"; private static final float REDUCED_SCALE = .5f; - private static final float LOW_RAM_REDUCED_SCALE = .6f; + private static final float LOW_RAM_REDUCED_SCALE = .8f; static final boolean DISABLE_FULL_SIZED_BITMAPS = ActivityManager.isLowRamDeviceStatic(); private static final long DELAY_MS = 100; private static final int QUALITY = 95; diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index f3880fa5dd09..3b2d51985a39 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -42,6 +42,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.logWithStack; +import static com.android.server.wm.WindowManagerService.sHierarchicalAnimations; import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM; import android.annotation.CallSuper; @@ -246,6 +247,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< private MagnificationSpec mLastMagnificationSpec; + private boolean mIsFocusable = true; + WindowContainer(WindowManagerService wms) { mWmService = wms; mPendingTransaction = wms.mTransactionFactory.get(); @@ -850,6 +853,21 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return false; } + @Override + boolean isFocusable() { + return super.isFocusable() && mIsFocusable; + } + + /** Set whether this container or its children can be focusable */ + @Override + boolean setFocusable(boolean focusable) { + if (mIsFocusable == focusable) { + return false; + } + mIsFocusable = focusable; + return true; + } + /** * @return Whether this child is on top of the window hierarchy. */ @@ -1855,7 +1873,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< // TODO: Remove this and use #getBounds() instead once we set an app transition animation // on TaskStack. Rect getAnimationBounds(int appStackClipMode) { - return getBounds(); + return getDisplayedBounds(); } /** @@ -1929,7 +1947,11 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< // Separate position and size for use in animators. mTmpRect.set(getAnimationBounds(appStackClipMode)); - mTmpPoint.set(mTmpRect.left, mTmpRect.top); + if (sHierarchicalAnimations) { + getRelativeDisplayedPosition(mTmpPoint); + } else { + mTmpPoint.set(mTmpRect.left, mTmpRect.top); + } mTmpRect.offsetTo(0, 0); final RemoteAnimationController controller = diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 74fdba1cd13e..e3b593e90fa5 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -4602,13 +4602,13 @@ public class WindowManagerService extends IWindowManager.Stub if (newFocus != null) { ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Gaining focus: %s", newFocus); - newFocus.reportFocusChangedSerialized(true, mInTouchMode); + newFocus.reportFocusChangedSerialized(true); notifyFocusChanged(); } if (lastFocus != null) { ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Losing focus: %s", lastFocus); - lastFocus.reportFocusChangedSerialized(false, mInTouchMode); + lastFocus.reportFocusChangedSerialized(false); } break; } @@ -4626,7 +4626,7 @@ public class WindowManagerService extends IWindowManager.Stub for (int i = 0; i < N; i++) { ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Losing delayed focus: %s", losers.get(i)); - losers.get(i).reportFocusChangedSerialized(false, mInTouchMode); + losers.get(i).reportFocusChangedSerialized(false); } break; } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index ba40f623ea66..36e9273a0904 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -657,6 +657,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP private KeyInterceptionInfo mKeyInterceptionInfo; /** + * This information is passed to SurfaceFlinger to decide which window should have a priority + * when deciding about the refresh rate of the display. All windows have the lowest priority by + * default. The variable is cached, so we do not send too many updates to SF. + */ + int mFrameRateSelectionPriority = RefreshRatePolicy.LAYER_PRIORITY_UNSET; + + /** * @return The insets state as requested by the client, i.e. the dispatched insets state * for which the visibilities are overridden with what the client requested. */ @@ -3339,11 +3346,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * Report a focus change. Must be called with no locks held, and consistently * from the same serialized thread (such as dispatched from a handler). */ - void reportFocusChangedSerialized(boolean focused, boolean inTouchMode) { - try { - mClient.windowFocusChanged(focused, inTouchMode); - } catch (RemoteException e) { - } + void reportFocusChangedSerialized(boolean focused) { if (mFocusCallbacks != null) { final int N = mFocusCallbacks.beginBroadcast(); for (int i=0; i<N; i++) { @@ -5169,6 +5172,24 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } } + + /** + * Notifies SF about the priority of the window, if it changed. SF then uses this information + * to decide which window's desired rendering rate should have a priority when deciding about + * the refresh rate of the screen. Priority + * {@link RefreshRatePolicy#LAYER_PRIORITY_FOCUSED_WITH_MODE} is considered the highest. + */ + @VisibleForTesting + void updateFrameRateSelectionPriorityIfNeeded() { + final int priority = getDisplayContent().getDisplayPolicy().getRefreshRatePolicy() + .calculatePriority(this); + if (mFrameRateSelectionPriority != priority) { + mFrameRateSelectionPriority = priority; + getPendingTransaction().setFrameRateSelectionPriority(mSurfaceControl, + mFrameRateSelectionPriority); + } + } + @Override void prepareSurfaces() { final Dimmer dimmer = getDimmer(); @@ -5177,6 +5198,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP applyDims(dimmer); } updateSurfacePosition(); + // Send information to SufaceFlinger about the priority of the current window. + updateFrameRateSelectionPriorityIfNeeded(); mWinAnimator.prepareSurfaceLocked(true); super.prepareSurfaces(); diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 03969b01f3f9..77d814e3076b 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -51,7 +51,7 @@ cc_library_static { "com_android_server_VibratorService.cpp", "com_android_server_PersistentDataBlockService.cpp", "com_android_server_GraphicsStatsService.cpp", - "com_android_server_am_AppCompactor.cpp", + "com_android_server_am_CachedAppOptimizer.cpp", "com_android_server_am_LowMemDetector.cpp", "com_android_server_incremental_IncrementalManagerService.cpp", "onload.cpp", diff --git a/services/core/jni/com_android_server_GraphicsStatsService.cpp b/services/core/jni/com_android_server_GraphicsStatsService.cpp index 9353fbd71214..7644adebb10a 100644 --- a/services/core/jni/com_android_server_GraphicsStatsService.cpp +++ b/services/core/jni/com_android_server_GraphicsStatsService.cpp @@ -178,15 +178,16 @@ static void writeGpuHistogram(stats_event* event, } // graphicsStatsPullCallback is invoked by statsd service to pull GRAPHICS_STATS atom. -static bool graphicsStatsPullCallback(int32_t atom_tag, pulled_stats_event_list* data, - const void* cookie) { +static status_pull_atom_return_t graphicsStatsPullCallback(int32_t atom_tag, + pulled_stats_event_list* data, + void* cookie) { JNIEnv* env = getJNIEnv(); if (!env) { return false; } if (gGraphicsStatsServiceObject == nullptr) { ALOGE("Failed to get graphicsstats service"); - return false; + return STATS_PULL_SKIP; } for (bool lastFullDay : {true, false}) { @@ -198,7 +199,7 @@ static bool graphicsStatsPullCallback(int32_t atom_tag, pulled_stats_event_list* env->ExceptionDescribe(); env->ExceptionClear(); ALOGE("Failed to invoke graphicsstats service"); - return false; + return STATS_PULL_SKIP; } if (!jdata) { // null means data is not available for that day. @@ -217,7 +218,7 @@ static bool graphicsStatsPullCallback(int32_t atom_tag, pulled_stats_event_list* if (!success) { ALOGW("Parse failed on GraphicsStatsPuller error='%s' dataSize='%d'", serviceDump.InitializationErrorString().c_str(), dataSize); - return false; + return STATS_PULL_SKIP; } for (int stat_index = 0; stat_index < serviceDump.stats_size(); stat_index++) { @@ -244,7 +245,7 @@ static bool graphicsStatsPullCallback(int32_t atom_tag, pulled_stats_event_list* stats_event_build(event); } } - return true; + return STATS_PULL_SUCCESS; } // Register a puller for GRAPHICS_STATS atom with the statsd service. diff --git a/services/core/jni/com_android_server_am_AppCompactor.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp index de6aa8b3266b..6a6da0e2b395 100644 --- a/services/core/jni/com_android_server_am_AppCompactor.cpp +++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#define LOG_TAG "AppCompactor" +#define LOG_TAG "CachedAppOptimizer" //#define LOG_NDEBUG 0 #include <dirent.h> @@ -42,7 +42,7 @@ namespace android { // or potentially some mainline modules. The only process that should definitely // not be compacted is system_server, since compacting system_server around the // time of BOOT_COMPLETE could result in perceptible issues. -static void com_android_server_am_AppCompactor_compactSystem(JNIEnv *, jobject) { +static void com_android_server_am_CachedAppOptimizer_compactSystem(JNIEnv *, jobject) { std::unique_ptr<DIR, decltype(&closedir)> proc(opendir("/proc"), closedir); struct dirent* current; while ((current = readdir(proc.get()))) { @@ -76,12 +76,12 @@ static void com_android_server_am_AppCompactor_compactSystem(JNIEnv *, jobject) static const JNINativeMethod sMethods[] = { /* name, signature, funcPtr */ - {"compactSystem", "()V", (void*)com_android_server_am_AppCompactor_compactSystem}, + {"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem}, }; -int register_android_server_am_AppCompactor(JNIEnv* env) +int register_android_server_am_CachedAppOptimizer(JNIEnv* env) { - return jniRegisterNativeMethods(env, "com/android/server/am/AppCompactor", + return jniRegisterNativeMethods(env, "com/android/server/am/CachedAppOptimizer", sMethods, NELEM(sMethods)); } diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp index acb6bea53a62..00436bb8ca70 100644 --- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp @@ -204,6 +204,7 @@ struct GnssDeathRecipient : virtual public hidl_death_recipient // Must match the value from GnssMeasurement.java static const uint32_t ADR_STATE_HALF_CYCLE_REPORTED = (1<<4); +static const uint32_t SVID_FLAGS_HAS_BASEBAND_CN0 = (1<<4); sp<GnssDeathRecipient> gnssHalDeathRecipient = nullptr; sp<IGnss_V1_0> gnssHal = nullptr; @@ -634,6 +635,16 @@ private: template<class T> Return<void> gnssSvStatusCbImpl(const T& svStatus); + template<class T> + uint32_t getHasBasebandCn0DbHzFlag(const T& svStatus) { + return 0; + } + + template<class T> + double getBasebandCn0DbHz(const T& svStatus, size_t i) { + return 0.0; + } + uint32_t getGnssSvInfoListSize(const IGnssCallback_V1_0::GnssSvStatus& svStatus) { return svStatus.numSvs; } @@ -658,8 +669,6 @@ private: const IGnssCallback_V1_0::GnssSvInfo& getGnssSvInfoOfIndex( const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>& svInfoList, size_t i) { - // TODO(b/144850155): fill baseband CN0 after it's available in Java object. - ALOGD("getGnssSvInfoOfIndex %d: baseband C/N0: %f", (int) i, svInfoList[i].basebandCN0DbHz); return svInfoList[i].v2_0.v1_0; } @@ -721,6 +730,18 @@ Return<void> GnssCallback::gnssStatusCb(const IGnssCallback_V2_0::GnssStatusValu return Void(); } +template<> +uint32_t GnssCallback::getHasBasebandCn0DbHzFlag(const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>& + svStatus) { + return SVID_FLAGS_HAS_BASEBAND_CN0; +} + +template<> +double GnssCallback::getBasebandCn0DbHz(const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>& svInfoList, + size_t i) { + return svInfoList[i].basebandCN0DbHz; +} + template<class T> Return<void> GnssCallback::gnssSvStatusCbImpl(const T& svStatus) { JNIEnv* env = getJniEnv(); @@ -758,8 +779,8 @@ Return<void> GnssCallback::gnssSvStatusCbImpl(const T& svStatus) { elev[i] = info.elevationDegrees; azim[i] = info.azimuthDegrees; carrierFreq[i] = info.carrierFrequencyHz; - // TODO(b/144850155): fill svidWithFlags with hasBasebandCn0DbHz based on HAL versions - basebandCn0s[i] = 0.0; + svidWithFlags[i] |= getHasBasebandCn0DbHzFlag(svStatus); + basebandCn0s[i] = getBasebandCn0DbHz(svStatus, i); } env->ReleaseIntArrayElements(svidWithFlagArray, svidWithFlags, 0); @@ -1185,8 +1206,8 @@ void GnssMeasurementCallback::translateSingleGnssMeasurement const IGnssMeasurementCallback_V2_1::GnssMeasurement* measurement_V2_1, JavaObject& object) { translateSingleGnssMeasurement(&(measurement_V2_1->v2_0), object); - // TODO(b/144850155): fill baseband CN0 after it's available in Java object - ALOGD("baseband CN0DbHz = %f\n", measurement_V2_1->basebandCN0DbHz); + + SET(BasebandCn0DbHz, measurement_V2_1->basebandCN0DbHz); } template<class T> diff --git a/services/core/jni/com_android_server_net_NetworkStatsService.cpp b/services/core/jni/com_android_server_net_NetworkStatsService.cpp index 4696dd0bb88b..0275f3ea32f7 100644 --- a/services/core/jni/com_android_server_net_NetworkStatsService.cpp +++ b/services/core/jni/com_android_server_net_NetworkStatsService.cpp @@ -33,7 +33,6 @@ #include "bpf/BpfUtils.h" #include "netdbpf/BpfNetworkStats.h" -using android::bpf::Stats; using android::bpf::bpfGetUidStats; using android::bpf::bpfGetIfaceStats; diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index c0a6e4e30f3a..19fa062bd9f9 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -54,7 +54,7 @@ int register_android_hardware_display_DisplayViewport(JNIEnv* env); int register_android_server_net_NetworkStatsFactory(JNIEnv* env); int register_android_server_net_NetworkStatsService(JNIEnv* env); int register_android_server_security_VerityUtils(JNIEnv* env); -int register_android_server_am_AppCompactor(JNIEnv* env); +int register_android_server_am_CachedAppOptimizer(JNIEnv* env); int register_android_server_am_LowMemDetector(JNIEnv* env); int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl( JNIEnv* env); @@ -106,7 +106,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_net_NetworkStatsFactory(env); register_android_server_net_NetworkStatsService(env); register_android_server_security_VerityUtils(env); - register_android_server_am_AppCompactor(env); + register_android_server_am_CachedAppOptimizer(env); register_android_server_am_LowMemDetector(env); register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl( env); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 6b81fdd2d270..b8b0dbf9157f 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -23,7 +23,6 @@ import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER; import static android.app.admin.DevicePolicyManager.CODE_ACCOUNTS_NOT_EMPTY; -import static android.app.admin.DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED; import static android.app.admin.DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE; import static android.app.admin.DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED; import static android.app.admin.DevicePolicyManager.CODE_HAS_DEVICE_OWNER; @@ -1096,7 +1095,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { String globalProxySpec = null; String globalProxyExclusionList = null; - ArrayMap<String, TrustAgentInfo> trustAgentInfos = new ArrayMap<>(); + @NonNull ArrayMap<String, TrustAgentInfo> trustAgentInfos = new ArrayMap<>(); List<String> crossProfileWidgetProviders; @@ -1651,6 +1650,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + @NonNull private ArrayMap<String, TrustAgentInfo> getAllTrustAgentInfos( XmlPullParser parser, String tag) throws XmlPullParserException, IOException { int outerDepthDAM = parser.getDepth(); @@ -2435,11 +2435,133 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { migrateUserRestrictionsIfNecessaryLocked(); // TODO PO may not have a class name either due to b/17652534. Address that too. - updateDeviceOwnerLocked(); } } + /** + * Checks if the device is in COMP mode, and if so migrates it to managed profile on a + * corporate owned device. + */ + @GuardedBy("getLockObject()") + private void maybeMigrateToProfileOnOrganizationOwnedDeviceLocked() { + logIfVerbose("Checking whether we need to migrate COMP "); + final int doUserId = mOwners.getDeviceOwnerUserId(); + if (doUserId == UserHandle.USER_NULL) { + logIfVerbose("No DO found, skipping migration."); + return; + } + + final List<UserInfo> profiles = mUserManager.getProfiles(doUserId); + if (profiles.size() != 2) { + if (profiles.size() == 1) { + logIfVerbose("Profile not found, skipping migration."); + } else { + Slog.wtf(LOG_TAG, "Found " + profiles.size() + " profiles, skipping migration"); + } + return; + } + + final int poUserId = getManagedUserId(doUserId); + if (poUserId < 0) { + Slog.wtf(LOG_TAG, "Found DO and a profile, but it is not managed, skipping migration"); + return; + } + + final ActiveAdmin doAdmin = getDeviceOwnerAdminLocked(); + final ActiveAdmin poAdmin = getProfileOwnerAdminLocked(poUserId); + if (doAdmin == null || poAdmin == null) { + Slog.wtf(LOG_TAG, "Failed to get either PO or DO admin, aborting migration."); + return; + } + + final ComponentName doAdminComponent = mOwners.getDeviceOwnerComponent(); + final ComponentName poAdminComponent = mOwners.getProfileOwnerComponent(poUserId); + if (doAdminComponent == null || poAdminComponent == null) { + Slog.wtf(LOG_TAG, "Cannot find PO or DO component name, aborting migration."); + return; + } + if (!doAdminComponent.getPackageName().equals(poAdminComponent.getPackageName())) { + Slog.e(LOG_TAG, "DO and PO are different packages, aborting migration."); + return; + } + + Slog.i(LOG_TAG, String.format( + "Migrating COMP to PO on a corp owned device; primary user: %d; profile: %d", + doUserId, poUserId)); + + Slog.i(LOG_TAG, "Giving the PO additional power..."); + markProfileOwnerOnOrganizationOwnedDeviceUncheckedLocked(poAdminComponent, poUserId); + Slog.i(LOG_TAG, "Migrating DO policies to PO..."); + moveDoPoliciesToProfileParentAdmin(doAdmin, poAdmin.getParentActiveAdmin()); + saveSettingsLocked(poUserId); + Slog.i(LOG_TAG, "Clearing the DO..."); + final ComponentName doAdminReceiver = doAdmin.info.getComponent(); + clearDeviceOwnerLocked(doAdmin, doUserId); + // TODO(b/143516163): If we have a power cut here, we might leave active admin. Consider if + // it is worth the complexity to make it more robust. + Slog.i(LOG_TAG, "Removing admin artifacts..."); + // TODO(b/143516163): Clean up application restrictions in UserManager. + removeAdminArtifacts(doAdminReceiver, doUserId); + Slog.i(LOG_TAG, "Migration complete."); + + // Note: KeyChain keys are not removed and will remain accessible for the apps that have + // been given grants to use them. + } + + private void moveDoPoliciesToProfileParentAdmin(ActiveAdmin doAdmin, ActiveAdmin parentAdmin) { + // The following policies can be already controlled via parent instance, skip if so. + if (parentAdmin.mPasswordPolicy.quality == PASSWORD_QUALITY_UNSPECIFIED) { + parentAdmin.mPasswordPolicy = doAdmin.mPasswordPolicy; + } + if (parentAdmin.passwordHistoryLength == ActiveAdmin.DEF_PASSWORD_HISTORY_LENGTH) { + parentAdmin.passwordHistoryLength = doAdmin.passwordHistoryLength; + } + if (parentAdmin.passwordExpirationTimeout == ActiveAdmin.DEF_PASSWORD_HISTORY_LENGTH) { + parentAdmin.passwordExpirationTimeout = doAdmin.passwordExpirationTimeout; + } + if (parentAdmin.maximumFailedPasswordsForWipe + == ActiveAdmin.DEF_MAXIMUM_FAILED_PASSWORDS_FOR_WIPE) { + parentAdmin.maximumFailedPasswordsForWipe = doAdmin.maximumFailedPasswordsForWipe; + } + if (parentAdmin.maximumTimeToUnlock == ActiveAdmin.DEF_MAXIMUM_TIME_TO_UNLOCK) { + parentAdmin.maximumTimeToUnlock = doAdmin.maximumTimeToUnlock; + } + if (parentAdmin.strongAuthUnlockTimeout + == DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS) { + parentAdmin.strongAuthUnlockTimeout = doAdmin.strongAuthUnlockTimeout; + } + parentAdmin.disabledKeyguardFeatures |= + doAdmin.disabledKeyguardFeatures & PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER; + + parentAdmin.trustAgentInfos.putAll(doAdmin.trustAgentInfos); + + // The following policies weren't available to PO, but will be available after migration. + parentAdmin.disableCamera = doAdmin.disableCamera; + + // TODO(b/143516163): Uncomment once corresponding APIs are available via parent instance. + // parentAdmin.disableScreenCapture = doAdmin.disableScreenCapture; + // parentAdmin.accountTypesWithManagementDisabled.addAll( + // doAdmin.accountTypesWithManagementDisabled); + + moveDoUserRestrictionsToCopeParent(doAdmin, parentAdmin); + + // TODO(b/143516163): migrate network and security logging state, currently they are + // turned off when DO is removed. + } + + private void moveDoUserRestrictionsToCopeParent(ActiveAdmin doAdmin, ActiveAdmin parentAdmin) { + if (doAdmin.userRestrictions == null) { + return; + } + for (final String restriction : doAdmin.userRestrictions.keySet()) { + if (UserRestrictionsUtils.canProfileOwnerOfOrganizationOwnedDeviceChange(restriction)) { + parentAdmin.userRestrictions.putBoolean( + restriction, doAdmin.userRestrictions.getBoolean(restriction)); + } + } + } + /** Apply default restrictions that haven't been applied to profile owners yet. */ private void maybeSetDefaultProfileOwnerUserRestrictions() { synchronized (getLockObject()) { @@ -3626,6 +3748,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { break; case SystemService.PHASE_ACTIVITY_MANAGER_READY: maybeStartSecurityLogMonitorOnActivityManagerReady(); + synchronized (getLockObject()) { + maybeMigrateToProfileOnOrganizationOwnedDeviceLocked(); + } break; case SystemService.PHASE_BOOT_COMPLETED: ensureDeviceOwnerUserStarted(); // TODO Consider better place to do this. @@ -4118,6 +4243,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER, userHandle)) { mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, false, userHandle); } + // When a device owner is set, the system automatically restricts adding a managed profile. + // Remove this restriction when the device owner is cleared. + if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, userHandle)) { + mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, false, + userHandle); + } } /** @@ -7451,8 +7582,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return; } Objects.requireNonNull(who, "ComponentName is null"); - // TODO (b/145286957) Refactor security checks - enforceDeviceOwnerOrProfileOwnerOnUser0OrProfileOwnerOrganizationOwned(); + enforceProfileOwnerOnUser0OrProfileOwnerOrganizationOwned(); mInjector.binderWithCleanCallingIdentity(() -> mInjector.settingsGlobalPutInt(Settings.Global.AUTO_TIME, enabled ? 1 : 0)); @@ -7473,7 +7603,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return false; } Objects.requireNonNull(who, "ComponentName is null"); - enforceDeviceOwnerOrProfileOwnerOnUser0OrProfileOwnerOrganizationOwned(); + enforceProfileOwnerOnUser0OrProfileOwnerOrganizationOwned(); return mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) > 0; } @@ -7487,8 +7617,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return; } Objects.requireNonNull(who, "ComponentName is null"); - // TODO (b/145286957) Refactor security checks - enforceDeviceOwnerOrProfileOwnerOnUser0OrProfileOwnerOrganizationOwned(); + enforceProfileOwnerOnUser0OrProfileOwnerOrganizationOwned(); mInjector.binderWithCleanCallingIdentity(() -> mInjector.settingsGlobalPutInt(Global.AUTO_TIME_ZONE, enabled ? 1 : 0)); @@ -7509,7 +7638,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return false; } Objects.requireNonNull(who, "ComponentName is null"); - enforceDeviceOwnerOrProfileOwnerOnUser0OrProfileOwnerOrganizationOwned(); + enforceProfileOwnerOnUser0OrProfileOwnerOrganizationOwned(); return mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) > 0; } @@ -8056,10 +8185,19 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { updateDeviceOwnerLocked(); setDeviceOwnerSystemPropertyLocked(); - // TODO Send to system too? - mInjector.binderWithCleanCallingIdentity( - () -> sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED, - userId)); + mInjector.binderWithCleanCallingIdentity(() -> { + // Restrict adding a managed profile when a device owner is set on the device. + // That is to prevent the co-existence of a managed profile and a device owner + // on the same device. + // Instead, the device may be provisioned with an organization-owned managed + // profile, such that the admin on that managed profile has extended management + // capabilities that can affect the entire device (but not access private data + // on the primary profile). + mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, true, + UserHandle.of(userId)); + // TODO Send to system too? + sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED, userId); + }); mDeviceAdminServiceController.startServiceForOwner( admin.getPackageName(), userId, "set-device-owner"); @@ -8322,6 +8460,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { throw new IllegalArgumentException("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 + // a profile - but it may not be a managed profile if there's a restriction on the + // parent to add managed profiles (e.g. if the device has a device owner). + if (parentUserId != userHandle && mUserManager.hasUserRestriction( + UserManager.DISALLOW_ADD_MANAGED_PROFILE, + UserHandle.of(parentUserId))) { + Slog.i(LOG_TAG, "Cannot set profile owner because of restriction."); + return false; + } + if (isAdb()) { // Log profile owner provisioning was started using adb. MetricsLogger.action(mContext, PROVISIONING_ENTRY_POINT_ADB, LOG_TAG_PROFILE_OWNER); @@ -8699,7 +8848,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!mHasFeature) { return false; } - enforceManageUsers(); return mInjector.binderWithCleanCallingIdentity(() -> { for (UserInfo ui : mUserManager.getUsers()) { @@ -9036,23 +9184,22 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { "Only profile owner, device owner and system may call this method."); } - private ActiveAdmin enforceDeviceOwnerOrProfileOwnerOnUser0OrProfileOwnerOrganizationOwned() { + private void enforceProfileOwnerOnUser0OrProfileOwnerOrganizationOwned() { synchronized (getLockObject()) { - // Check if there is a device owner - ActiveAdmin deviceOwner = getActiveAdminWithPolicyForUidLocked(null, - DeviceAdminInfo.USES_POLICY_DEVICE_OWNER, mInjector.binderGetCallingUid()); - if (deviceOwner != null) return deviceOwner; + // Check if there is a device owner or profile owner of an organization-owned device + ActiveAdmin owner = getActiveAdminWithPolicyForUidLocked(null, + DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER, + mInjector.binderGetCallingUid()); + if (owner != null) { + return; + } - ActiveAdmin profileOwner = getActiveAdminWithPolicyForUidLocked(null, + // Checks whether the caller is a profile owner on user 0 rather than + // checking whether the active admin is on user 0 + owner = getActiveAdminWithPolicyForUidLocked(null, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, mInjector.binderGetCallingUid()); - - // Check if there is a profile owner of an organization owned device - if (isProfileOwnerOfOrganizationOwnedDevice(profileOwner)) return profileOwner; - - // Check if there is a profile owner called on user 0 - if (profileOwner != null) { - enforceCallerSystemUserHandle(); - return profileOwner; + if (owner != null && owner.getUserHandle().isSystem()) { + return; } } throw new SecurityException("No active admin found"); @@ -12387,25 +12534,41 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final long ident = mInjector.binderClearCallingIdentity(); try { final UserHandle callingUserHandle = UserHandle.of(callingUserId); - final ComponentName ownerAdmin = getOwnerComponent(packageName, callingUserId); - if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, - callingUserHandle)) { - // An admin can initiate provisioning if it has set the restriction. - if (ownerAdmin == null || isAdminAffectedByRestriction(ownerAdmin, - UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserId)) { - return CODE_ADD_MANAGED_PROFILE_DISALLOWED; - } - } - boolean canRemoveProfile = true; - if (mUserManager.hasUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, - callingUserHandle)) { - // We can remove a profile if the admin itself has set the restriction. - if (ownerAdmin == null || isAdminAffectedByRestriction(ownerAdmin, - UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, - callingUserId)) { - canRemoveProfile = false; - } + final boolean hasDeviceOwner; + synchronized (getLockObject()) { + hasDeviceOwner = getDeviceOwnerAdminLocked() != null; } + + final boolean addingProfileRestricted = mUserManager.hasUserRestriction( + UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserHandle); + + UserInfo parentUser = mUserManager.getProfileParent(callingUserId); + final boolean addingProfileRestrictedOnParent = (parentUser != null) + && mUserManager.hasUserRestriction( + UserManager.DISALLOW_ADD_MANAGED_PROFILE, + UserHandle.of(parentUser.id)); + + Slog.i(LOG_TAG, String.format( + "When checking for managed profile provisioning: Has device owner? %b, adding" + + " profile restricted? %b, adding profile restricted on parent? %b", + hasDeviceOwner, addingProfileRestricted, addingProfileRestrictedOnParent)); + + // If there's a device owner, the restriction on adding a managed profile must be set + // somewhere. + if (hasDeviceOwner && !addingProfileRestricted && !addingProfileRestrictedOnParent) { + Slog.wtf(LOG_TAG, "Has a device owner but no restriction on adding a profile."); + } + + // Do not allow adding a managed profile if there's a restriction, either on the current + // user or its parent user. + if (addingProfileRestricted || addingProfileRestrictedOnParent) { + return CODE_CANNOT_ADD_MANAGED_PROFILE; + } + // If there's a restriction on removing the managed profile then we have to take it + // into account when checking whether more profiles can be added. + boolean canRemoveProfile = + !mUserManager.hasUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, + callingUserHandle); if (!mUserManager.canAddMoreManagedProfiles(callingUserId, canRemoveProfile)) { return CODE_CANNOT_ADD_MANAGED_PROFILE; } @@ -12882,37 +13045,43 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // Grant access under lock. synchronized (getLockObject()) { - // Sanity check: Make sure that the user has a profile owner and that the specified - // component is the profile owner of that user. - if (!isProfileOwner(who, userId)) { - throw new IllegalArgumentException(String.format( - "Component %s is not a Profile Owner of user %d", - who.flattenToString(), userId)); - } + markProfileOwnerOnOrganizationOwnedDeviceUncheckedLocked(who, userId); + } + } - Slog.i(LOG_TAG, String.format( - "Marking %s as profile owner on organization-owned device for user %d", + @GuardedBy("getLockObject()") + private void markProfileOwnerOnOrganizationOwnedDeviceUncheckedLocked( + ComponentName who, int userId) { + // Sanity check: Make sure that the user has a profile owner and that the specified + // component is the profile owner of that user. + if (!isProfileOwner(who, userId)) { + throw new IllegalArgumentException(String.format( + "Component %s is not a Profile Owner of user %d", who.flattenToString(), userId)); + } - // First, set restriction on removing the profile. - mInjector.binderWithCleanCallingIdentity(() -> { - // Clear restriction as user. - UserHandle parentUser = mUserManager.getProfileParent(UserHandle.of(userId)); - if (!parentUser.isSystem()) { - throw new IllegalStateException( - String.format("Only the profile owner of a managed profile on the" + Slog.i(LOG_TAG, String.format( + "Marking %s as profile owner on organization-owned device for user %d", + who.flattenToString(), userId)); + + // First, set restriction on removing the profile. + mInjector.binderWithCleanCallingIdentity(() -> { + // Clear restriction as user. + final UserHandle parentUser = mUserManager.getProfileParent(UserHandle.of(userId)); + if (!parentUser.isSystem()) { + throw new IllegalStateException( + String.format("Only the profile owner of a managed profile on the" + " primary user can be granted access to device identifiers, not" + " on user %d", parentUser.getIdentifier())); - } + } - mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, true, - parentUser); - }); + mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, true, + parentUser); + }); - // markProfileOwnerOfOrganizationOwnedDevice will trigger writing of the profile owner - // data, no need to do it manually. - mOwners.markProfileOwnerOfOrganizationOwnedDevice(userId); - } + // markProfileOwnerOfOrganizationOwnedDevice will trigger writing of the profile owner + // data, no need to do it manually. + mOwners.markProfileOwnerOfOrganizationOwnedDevice(userId); } private void pushMeteredDisabledPackagesLocked(int userId) { @@ -14876,4 +15045,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return packages == null ? Collections.EMPTY_LIST : packages; } } + + private void logIfVerbose(String message) { + if (VERBOSE_LOG) { + Slog.d(LOG_TAG, message); + } + } } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index b6a8ca447213..109a6fd528b8 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -71,11 +71,11 @@ import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Slog; +import android.util.StatsLog; import android.view.WindowManager; import android.view.contentcapture.ContentCaptureManager; import com.android.internal.R; -import com.android.internal.logging.MetricsLogger; import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.BinderInternal; import com.android.internal.util.ConcurrentUtils; @@ -213,10 +213,12 @@ public final class SystemServer { "com.android.server.print.PrintManagerService"; private static final String COMPANION_DEVICE_MANAGER_SERVICE_CLASS = "com.android.server.companion.CompanionDeviceManagerService"; + private static final String STATS_COMPANION_APEX_PATH = + "/apex/com.android.os.statsd/javalib/service-statsd.jar"; private static final String STATS_COMPANION_LIFECYCLE_CLASS = "com.android.server.stats.StatsCompanion$Lifecycle"; private static final String STATS_PULL_ATOM_SERVICE_CLASS = - "com.android.server.stats.StatsPullAtomService"; + "com.android.server.stats.pull.StatsPullAtomService"; private static final String USB_SERVICE_CLASS = "com.android.server.usb.UsbService$Lifecycle"; private static final String MIDI_SERVICE_CLASS = @@ -441,10 +443,12 @@ public final class SystemServer { // Here we go! Slog.i(TAG, "Entered the Android system server!"); - int uptimeMillis = (int) SystemClock.elapsedRealtime(); + final long uptimeMillis = SystemClock.elapsedRealtime(); EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_SYSTEM_RUN, uptimeMillis); if (!mRuntimeRestart) { - MetricsLogger.histogram(null, "boot_system_server_init", uptimeMillis); + StatsLog.write(StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED, + StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__SYSTEM_SERVER_INIT_START, + uptimeMillis); } // In case the runtime switched since last boot (such as when @@ -553,10 +557,12 @@ public final class SystemServer { StrictMode.initVmDefaults(null); if (!mRuntimeRestart && !isFirstBootOrUpgrade()) { - int uptimeMillis = (int) SystemClock.elapsedRealtime(); - MetricsLogger.histogram(null, "boot_system_server_ready", uptimeMillis); - final int MAX_UPTIME_MILLIS = 60 * 1000; - if (uptimeMillis > MAX_UPTIME_MILLIS) { + final long uptimeMillis = SystemClock.elapsedRealtime(); + StatsLog.write(StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED, + StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__SYSTEM_SERVER_READY, + uptimeMillis); + final long maxUptimeMillis = 60 * 1000; + if (uptimeMillis > maxUptimeMillis) { Slog.wtf(SYSTEM_SERVER_TIMING_TAG, "SystemServer init took too long. uptimeMillis=" + uptimeMillis); } @@ -752,7 +758,7 @@ public final class SystemServer { // note that we just booted, which might send out a rescue party if // we're stuck in a runtime restart loop. RescueParty.registerHealthObserver(mSystemContext); - RescueParty.noteBoot(mSystemContext); + PackageWatchdog.getInstance(mSystemContext).noteBoot(); // Manages LEDs and display backlight so we need it to bring up the display. t.traceBegin("StartLightsService"); @@ -789,8 +795,9 @@ public final class SystemServer { // Start the package manager. if (!mRuntimeRestart) { - MetricsLogger.histogram(null, "boot_package_manager_init_start", - (int) SystemClock.elapsedRealtime()); + StatsLog.write(StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED, + StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__PACKAGE_MANAGER_INIT_START, + SystemClock.elapsedRealtime()); } t.traceBegin("StartPackageManagerService"); @@ -806,8 +813,9 @@ public final class SystemServer { mPackageManager = mSystemContext.getPackageManager(); t.traceEnd(); if (!mRuntimeRestart && !isFirstBootOrUpgrade()) { - MetricsLogger.histogram(null, "boot_package_manager_init_ready", - (int) SystemClock.elapsedRealtime()); + StatsLog.write(StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED, + StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__PACKAGE_MANAGER_INIT_READY, + SystemClock.elapsedRealtime()); } // Manages A/B OTA dexopting. This is a bootstrap service as we need it to rename // A/B artifacts after boot, before anything else might touch/need them. @@ -1980,7 +1988,8 @@ public final class SystemServer { // Statsd helper t.traceBegin("StartStatsCompanion"); - mSystemServiceManager.startService(STATS_COMPANION_LIFECYCLE_CLASS); + mSystemServiceManager.startServiceFromJar( + STATS_COMPANION_LIFECYCLE_CLASS, STATS_COMPANION_APEX_PATH); t.traceEnd(); // Statsd pulled atoms diff --git a/services/net/Android.bp b/services/net/Android.bp index 9c7cfc197bba..cf84bdfb5b6f 100644 --- a/services/net/Android.bp +++ b/services/net/Android.bp @@ -23,7 +23,6 @@ filegroup { name: "services-tethering-shared-srcs", srcs: [ ":framework-annotations", - "java/android/net/util/NetdService.java", "java/android/net/util/NetworkConstants.java", ], visibility: ["//frameworks/base/packages/Tethering"], diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java index ef3da6085fb1..9569c6e8be89 100644 --- a/services/people/java/com/android/server/people/PeopleService.java +++ b/services/people/java/com/android/server/people/PeopleService.java @@ -137,5 +137,14 @@ public class PeopleService extends SystemService { Slog.e(TAG, "Failed to calling callback" + e); } } + + @Override + public byte[] backupConversationInfos(int userId) { + return new byte[0]; + } + + @Override + public void restoreConversationInfos(int userId, String key, byte[] payload) { + } } } diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp index 96fedf933a9c..3d9f11ff6d2f 100644 --- a/services/tests/mockingservicestests/Android.bp +++ b/services/tests/mockingservicestests/Android.bp @@ -21,6 +21,7 @@ android_test { "services.core", "services.net", "service-jobscheduler", + "service-permission", "androidx.test.runner", "mockito-target-extended-minus-junit4", "platform-test-annotations", diff --git a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java index 556f96ace5d2..6a5de84266e2 100644 --- a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java @@ -345,8 +345,8 @@ public class AlarmManagerServiceTest { } /** - * Lowers quotas to make testing feasible. - * Careful while calling as this will replace any existing settings for the calling test. + * Lowers quotas to make testing feasible. Careful while calling as this will replace any + * existing settings for the calling test. */ private void setTestableQuotas() { final StringBuilder constantsBuilder = new StringBuilder(); @@ -981,6 +981,25 @@ public class AlarmManagerServiceTest { assertEquals(0, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); } + @Test + public void alarmCountOnListenerBinderDied() { + final int numAlarms = 10; + final IAlarmListener[] listeners = new IAlarmListener[numAlarms]; + for (int i = 0; i < numAlarms; i++) { + listeners[i] = new IAlarmListener.Stub() { + @Override + public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { + } + }; + setTestAlarmWithListener(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + i, listeners[i]); + } + assertEquals(numAlarms, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); + for (int i = 0; i < numAlarms; i++) { + mService.mListenerDeathRecipient.binderDied(listeners[i].asBinder()); + assertEquals(numAlarms - i - 1, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); + } + } + @After public void tearDown() { if (mMockingSession != null) { diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java index 30d89d31ba64..c3602f8db9bc 100644 --- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java @@ -24,6 +24,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; +import static com.android.server.RescueParty.LEVEL_FACTORY_RESET; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -144,7 +145,6 @@ public class RescuePartyTest { doReturn(CURRENT_NETWORK_TIME_MILLIS).when(() -> RescueParty.getElapsedRealtime()); - RescueParty.resetAllThresholds(); FlagNamespaceUtils.resetKnownResetNamespacesFlagCounterForTest(); SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, @@ -160,28 +160,28 @@ public class RescuePartyTest { @Test public void testBootLoopDetectionWithExecutionForAllRescueLevels() { - noteBoot(RescueParty.TRIGGER_COUNT); + noteBoot(); verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS); assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS, SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); - noteBoot(RescueParty.TRIGGER_COUNT); + noteBoot(); verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES); assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES, SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); - noteBoot(RescueParty.TRIGGER_COUNT); + noteBoot(); verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS); assertEquals(RescueParty.LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS, SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); - noteBoot(RescueParty.TRIGGER_COUNT); + noteBoot(); verify(() -> RecoverySystem.rebootPromptAndWipeUserData(mMockContext, RescueParty.TAG)); - assertEquals(RescueParty.LEVEL_FACTORY_RESET, + assertEquals(LEVEL_FACTORY_RESET, SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); } @@ -208,48 +208,15 @@ public class RescuePartyTest { notePersistentAppCrash(); verify(() -> RecoverySystem.rebootPromptAndWipeUserData(mMockContext, RescueParty.TAG)); - assertEquals(RescueParty.LEVEL_FACTORY_RESET, - SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); - } - - @Test - public void testBootLoopDetectionWithWrongInterval() { - noteBoot(RescueParty.TRIGGER_COUNT - 1); - - // last boot is just outside of the boot loop detection window - doReturn(CURRENT_NETWORK_TIME_MILLIS + RescueParty.BOOT_TRIGGER_WINDOW_MILLIS + 1).when( - () -> RescueParty.getElapsedRealtime()); - noteBoot(/*numTimes=*/1); - - assertEquals(RescueParty.LEVEL_NONE, - SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); - } - - @Test - public void testBootLoopDetectionWithProperInterval() { - noteBoot(RescueParty.TRIGGER_COUNT - 1); - - // last boot is just inside of the boot loop detection window - doReturn(CURRENT_NETWORK_TIME_MILLIS + RescueParty.BOOT_TRIGGER_WINDOW_MILLIS).when( - () -> RescueParty.getElapsedRealtime()); - noteBoot(/*numTimes=*/1); - - verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS); - assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS, - SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); - } - - @Test - public void testBootLoopDetectionWithWrongTriggerCount() { - noteBoot(RescueParty.TRIGGER_COUNT - 1); - assertEquals(RescueParty.LEVEL_NONE, + assertEquals(LEVEL_FACTORY_RESET, SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); } @Test public void testIsAttemptingFactoryReset() { - noteBoot(RescueParty.TRIGGER_COUNT * 4); - + for (int i = 0; i < LEVEL_FACTORY_RESET; i++) { + noteBoot(); + } verify(() -> RecoverySystem.rebootPromptAndWipeUserData(mMockContext, RescueParty.TAG)); assertTrue(RescueParty.isAttemptingFactoryReset()); } @@ -306,7 +273,7 @@ public class RescuePartyTest { // Ensure that no action is taken for cases where the failure reason is unknown SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString( - RescueParty.LEVEL_FACTORY_RESET)); + LEVEL_FACTORY_RESET)); assertEquals(observer.onHealthCheckFailed(null, PackageWatchdog.FAILURE_REASON_UNKNOWN), PackageHealthObserverImpact.USER_IMPACT_NONE); @@ -342,7 +309,7 @@ public class RescuePartyTest { SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString( - RescueParty.LEVEL_FACTORY_RESET)); + LEVEL_FACTORY_RESET)); assertEquals(observer.onHealthCheckFailed(null, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING), PackageHealthObserverImpact.USER_IMPACT_HIGH); @@ -366,10 +333,8 @@ public class RescuePartyTest { eq(resetMode), anyInt())); } - private void noteBoot(int numTimes) { - for (int i = 0; i < numTimes; i++) { - RescueParty.noteBoot(mMockContext); - } + private void noteBoot() { + RescuePartyObserver.getInstance(mMockContext).executeBootLoopMitigation(); } private void notePersistentAppCrash() { diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java deleted file mode 100644 index 48e459ff2d9d..000000000000 --- a/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java +++ /dev/null @@ -1,680 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.am; - -import static com.android.server.am.ActivityManagerService.Injector; -import static com.android.server.am.AppCompactor.compactActionIntToString; - -import static com.google.common.truth.Truth.assertThat; - -import android.content.Context; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Process; -import android.platform.test.annotations.Presubmit; -import android.provider.DeviceConfig; -import android.text.TextUtils; - -import androidx.test.platform.app.InstrumentationRegistry; - -import com.android.server.ServiceThread; -import com.android.server.appop.AppOpsService; -import com.android.server.testables.TestableDeviceConfig; - -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; - -import java.io.File; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -/** - * Tests for {@link AppCompactor}. - * - * Build/Install/Run: - * atest FrameworksMockingServicesTests:AppCompactorTest - */ -@Presubmit -@RunWith(MockitoJUnitRunner.class) -public final class AppCompactorTest { - - private ServiceThread mThread; - - @Mock - private AppOpsService mAppOpsService; - private AppCompactor mCompactorUnderTest; - private HandlerThread mHandlerThread; - private Handler mHandler; - private CountDownLatch mCountDown; - - @Rule - public TestableDeviceConfig.TestableDeviceConfigRule - mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule(); - - @Before - public void setUp() { - mHandlerThread = new HandlerThread(""); - mHandlerThread.start(); - mHandler = new Handler(mHandlerThread.getLooper()); - - mThread = new ServiceThread("TestServiceThread", Process.THREAD_PRIORITY_DEFAULT, - true /* allowIo */); - mThread.start(); - - ActivityManagerService ams = new ActivityManagerService( - new TestInjector(InstrumentationRegistry.getInstrumentation().getContext()), - mThread); - mCompactorUnderTest = new AppCompactor(ams, - new AppCompactor.PropertyChangedCallbackForTest() { - @Override - public void onPropertyChanged() { - if (mCountDown != null) { - mCountDown.countDown(); - } - } - }); - } - - @After - public void tearDown() { - mHandlerThread.quit(); - mThread.quit(); - mCountDown = null; - } - - @Test - public void init_setsDefaults() { - mCompactorUnderTest.init(); - assertThat(mCompactorUnderTest.useCompaction()).isEqualTo( - AppCompactor.DEFAULT_USE_COMPACTION); - assertThat(mCompactorUnderTest.mCompactActionSome).isEqualTo( - compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_1)); - assertThat(mCompactorUnderTest.mCompactActionFull).isEqualTo( - compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_2)); - assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_1); - assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_2); - assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_3); - assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_4); - assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo( - AppCompactor.DEFAULT_STATSD_SAMPLE_RATE); - assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo( - AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB); - assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_5); - assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_6); - assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo( - AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB); - assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo( - AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB); - - Set<Integer> expected = new HashSet<>(); - for (String s : TextUtils.split(AppCompactor.DEFAULT_COMPACT_PROC_STATE_THROTTLE, ",")) { - expected.add(Integer.parseInt(s)); - } - assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected); - } - - @Test - public void init_withDeviceConfigSetsParameters() { - // When the DeviceConfig already has a flag value stored (note this test will need to - // change if the default value changes from false). - assertThat(AppCompactor.DEFAULT_USE_COMPACTION).isFalse(); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_USE_COMPACTION, "true", false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_ACTION_1, - Integer.toString((AppCompactor.DEFAULT_COMPACT_ACTION_1 + 1 % 4) + 1), false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_ACTION_2, - Integer.toString((AppCompactor.DEFAULT_COMPACT_ACTION_2 + 1 % 4) + 1), false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_THROTTLE_1, - Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1), false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_THROTTLE_2, - Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1), false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_THROTTLE_3, - Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1), false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_THROTTLE_4, - Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1), false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_THROTTLE_5, - Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1), false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_THROTTLE_6, - Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1), false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE, - Float.toString(AppCompactor.DEFAULT_STATSD_SAMPLE_RATE + 0.1f), false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB, - Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1), false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, - Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1), false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,2,3", false); - - // Then calling init will read and set that flag. - mCompactorUnderTest.init(); - assertThat(mCompactorUnderTest.useCompaction()).isTrue(); - assertThat(mCompactorUnderTest.mCompactionThread.isAlive()).isTrue(); - - assertThat(mCompactorUnderTest.mCompactActionSome).isEqualTo( - compactActionIntToString((AppCompactor.DEFAULT_COMPACT_ACTION_1 + 1 % 4) + 1)); - assertThat(mCompactorUnderTest.mCompactActionFull).isEqualTo( - compactActionIntToString((AppCompactor.DEFAULT_COMPACT_ACTION_2 + 1 % 4) + 1)); - assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1); - assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1); - assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1); - assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1); - assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1); - assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1); - assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo( - AppCompactor.DEFAULT_STATSD_SAMPLE_RATE + 0.1f); - assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1); - assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1); - assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo( - AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1); - assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactly(1, 2, 3); - } - - @Test - public void useCompaction_listensToDeviceConfigChanges() throws InterruptedException { - assertThat(mCompactorUnderTest.useCompaction()).isEqualTo( - AppCompactor.DEFAULT_USE_COMPACTION); - // When we call init and change some the flag value... - mCompactorUnderTest.init(); - mCountDown = new CountDownLatch(1); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_USE_COMPACTION, "true", false); - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); - - // Then that new flag value is updated in the implementation. - assertThat(mCompactorUnderTest.useCompaction()).isTrue(); - assertThat(mCompactorUnderTest.mCompactionThread.isAlive()).isTrue(); - - // And again, setting the flag the other way. - mCountDown = new CountDownLatch(1); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_USE_COMPACTION, "false", false); - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); - assertThat(mCompactorUnderTest.useCompaction()).isFalse(); - } - - @Test - public void useCompaction_listensToDeviceConfigChangesBadValues() throws InterruptedException { - assertThat(mCompactorUnderTest.useCompaction()).isEqualTo( - AppCompactor.DEFAULT_USE_COMPACTION); - mCompactorUnderTest.init(); - - // When we push an invalid flag value... - mCountDown = new CountDownLatch(1); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_USE_COMPACTION, "foobar", false); - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); - - // Then we set the default. - assertThat(mCompactorUnderTest.useCompaction()).isEqualTo( - AppCompactor.DEFAULT_USE_COMPACTION); - } - - @Test - public void compactAction_listensToDeviceConfigChanges() throws InterruptedException { - mCompactorUnderTest.init(); - - // When we override new values for the compaction action with reasonable values... - - // There are four possible values for compactAction[Some|Full]. - for (int i = 1; i < 5; i++) { - mCountDown = new CountDownLatch(2); - int expectedSome = (AppCompactor.DEFAULT_COMPACT_ACTION_1 + i) % 4 + 1; - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_ACTION_1, Integer.toString(expectedSome), false); - int expectedFull = (AppCompactor.DEFAULT_COMPACT_ACTION_2 + i) % 4 + 1; - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_ACTION_2, Integer.toString(expectedFull), false); - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); - - // Then the updates are reflected in the flags. - assertThat(mCompactorUnderTest.mCompactActionSome).isEqualTo( - compactActionIntToString(expectedSome)); - assertThat(mCompactorUnderTest.mCompactActionFull).isEqualTo( - compactActionIntToString(expectedFull)); - } - } - - @Test - public void compactAction_listensToDeviceConfigChangesBadValues() throws InterruptedException { - mCompactorUnderTest.init(); - - // When we override new values for the compaction action with bad values ... - mCountDown = new CountDownLatch(2); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_ACTION_1, "foo", false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_ACTION_2, "foo", false); - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); - - // Then the default values are reflected in the flag - assertThat(mCompactorUnderTest.mCompactActionSome).isEqualTo( - compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_1)); - assertThat(mCompactorUnderTest.mCompactActionFull).isEqualTo( - compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_2)); - - mCountDown = new CountDownLatch(2); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_ACTION_1, "", false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_ACTION_2, "", false); - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); - - assertThat(mCompactorUnderTest.mCompactActionSome).isEqualTo( - compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_1)); - assertThat(mCompactorUnderTest.mCompactActionFull).isEqualTo( - compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_2)); - } - - @Test - public void compactThrottle_listensToDeviceConfigChanges() throws InterruptedException { - mCompactorUnderTest.init(); - - // When we override new reasonable throttle values after init... - mCountDown = new CountDownLatch(6); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_THROTTLE_1, - Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1), false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_THROTTLE_2, - Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1), false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_THROTTLE_3, - Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1), false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_THROTTLE_4, - Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1), false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_THROTTLE_5, - Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1), false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_THROTTLE_6, - Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1), false); - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); - - // Then those flags values are reflected in the compactor. - assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1); - assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1); - assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1); - assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1); - assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1); - assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1); - } - - @Test - public void compactThrottle_listensToDeviceConfigChangesBadValues() - throws InterruptedException { - mCompactorUnderTest.init(); - - // When one of the throttles is overridden with a bad value... - mCountDown = new CountDownLatch(1); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_THROTTLE_1, "foo", false); - // Then all the throttles have the defaults set. - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); - assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_1); - assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_2); - assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_3); - assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_4); - assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_5); - assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_6); - - // Repeat for each of the throttle keys. - mCountDown = new CountDownLatch(1); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_THROTTLE_2, "foo", false); - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); - assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_1); - assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_2); - assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_3); - assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_4); - assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_5); - assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_6); - - mCountDown = new CountDownLatch(1); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_THROTTLE_3, "foo", false); - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); - assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_1); - assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_2); - assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_3); - assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_4); - assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_5); - assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_6); - - mCountDown = new CountDownLatch(1); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_THROTTLE_4, "foo", false); - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); - assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_1); - assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_2); - assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_3); - assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_4); - assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_5); - assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_6); - - mCountDown = new CountDownLatch(1); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_THROTTLE_5, "foo", false); - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); - assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_1); - assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_2); - assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_3); - assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_4); - assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_5); - assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_6); - - mCountDown = new CountDownLatch(1); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_THROTTLE_6, "foo", false); - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); - assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_1); - assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_2); - assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_3); - assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_4); - assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_5); - assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo( - AppCompactor.DEFAULT_COMPACT_THROTTLE_6); - } - - @Test - public void statsdSampleRate_listensToDeviceConfigChanges() throws InterruptedException { - mCompactorUnderTest.init(); - - // When we override mStatsdSampleRate with a reasonable value ... - mCountDown = new CountDownLatch(1); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE, - Float.toString(AppCompactor.DEFAULT_STATSD_SAMPLE_RATE + 0.1f), false); - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); - - // Then that override is reflected in the compactor. - assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo( - AppCompactor.DEFAULT_STATSD_SAMPLE_RATE + 0.1f); - } - - @Test - public void statsdSampleRate_listensToDeviceConfigChangesBadValues() - throws InterruptedException { - mCompactorUnderTest.init(); - - // When we override mStatsdSampleRate with an unreasonable value ... - mCountDown = new CountDownLatch(1); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE, "foo", false); - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); - - // Then that override is reflected in the compactor. - assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo( - AppCompactor.DEFAULT_STATSD_SAMPLE_RATE); - } - - @Test - public void statsdSampleRate_listensToDeviceConfigChangesOutOfRangeValues() - throws InterruptedException { - mCompactorUnderTest.init(); - - // When we override mStatsdSampleRate with an value outside of [0..1]... - mCountDown = new CountDownLatch(1); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE, - Float.toString(-1.0f), false); - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); - - // Then the values is capped in the range. - assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(0.0f); - - mCountDown = new CountDownLatch(1); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE, - Float.toString(1.01f), false); - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); - - // Then the values is capped in the range. - assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(1.0f); - } - - @Test - public void fullCompactionRssThrottleKb_listensToDeviceConfigChanges() - throws InterruptedException { - mCompactorUnderTest.init(); - - // When we override mStatsdSampleRate with a reasonable value ... - mCountDown = new CountDownLatch(1); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB, - Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1), false); - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); - - // Then that override is reflected in the compactor. - assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo( - AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1); - } - - @Test - public void fullCompactionRssThrottleKb_listensToDeviceConfigChangesBadValues() - throws InterruptedException { - mCompactorUnderTest.init(); - - // When we override mStatsdSampleRate with an unreasonable value ... - mCountDown = new CountDownLatch(1); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "foo", false); - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); - - // Then that override is reflected in the compactor. - assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo( - AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB); - - mCountDown = new CountDownLatch(1); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "-100", false); - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); - - // Then that override is reflected in the compactor. - assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo( - AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB); - } - - @Test - public void fullCompactionDeltaRssThrottleKb_listensToDeviceConfigChanges() - throws InterruptedException { - mCompactorUnderTest.init(); - - // When we override mStatsdSampleRate with a reasonable value ... - mCountDown = new CountDownLatch(1); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, - Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1), false); - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); - - // Then that override is reflected in the compactor. - assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo( - AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1); - } - - @Test - public void fullCompactionDeltaRssThrottleKb_listensToDeviceConfigChangesBadValues() - throws InterruptedException { - mCompactorUnderTest.init(); - - // When we override mStatsdSampleRate with an unreasonable value ... - mCountDown = new CountDownLatch(1); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "foo", false); - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); - - // Then that override is reflected in the compactor. - assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo( - AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB); - - mCountDown = new CountDownLatch(1); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "-100", false); - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); - - // Then that override is reflected in the compactor. - assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo( - AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB); - } - - @Test - public void procStateThrottle_listensToDeviceConfigChanges() - throws InterruptedException { - mCompactorUnderTest.init(); - mCountDown = new CountDownLatch(1); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,2,3", false); - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); - assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactly(1, 2, 3); - - mCountDown = new CountDownLatch(1); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "", false); - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); - assertThat(mCompactorUnderTest.mProcStateThrottle).isEmpty(); - } - - @Test - public void procStateThrottle_listensToDeviceConfigChangesBadValues() - throws InterruptedException { - mCompactorUnderTest.init(); - - Set<Integer> expected = new HashSet<>(); - for (String s : TextUtils.split(AppCompactor.DEFAULT_COMPACT_PROC_STATE_THROTTLE, ",")) { - expected.add(Integer.parseInt(s)); - } - - // Not numbers - mCountDown = new CountDownLatch(1); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "foo", false); - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); - assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected); - mCountDown = new CountDownLatch(1); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,foo", false); - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); - assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected); - - // Empty splits - mCountDown = new CountDownLatch(1); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, ",", false); - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); - assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected); - mCountDown = new CountDownLatch(1); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, ",,3", false); - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); - assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected); - mCountDown = new CountDownLatch(1); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,,3", false); - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); - assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected); - } - - private class TestInjector extends Injector { - - TestInjector(Context context) { - super(context); - } - - @Override - public AppOpsService getAppOpsService(File file, Handler handler) { - return mAppOpsService; - } - - @Override - public Handler getUiHandler(ActivityManagerService service) { - return mHandler; - } - } -} diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java new file mode 100644 index 000000000000..f037692886ab --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java @@ -0,0 +1,692 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import static com.android.server.am.ActivityManagerService.Injector; +import static com.android.server.am.CachedAppOptimizer.compactActionIntToString; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Process; +import android.platform.test.annotations.Presubmit; +import android.provider.DeviceConfig; +import android.text.TextUtils; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.ServiceThread; +import com.android.server.appop.AppOpsService; +import com.android.server.testables.TestableDeviceConfig; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.io.File; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Tests for {@link CachedAppOptimizer}. + * + * Build/Install/Run: + * atest FrameworksMockingServicesTests:CachedAppOptimizerTest + */ +@Presubmit +@RunWith(MockitoJUnitRunner.class) +public final class CachedAppOptimizerTest { + + private ServiceThread mThread; + + @Mock + private AppOpsService mAppOpsService; + private CachedAppOptimizer mCachedAppOptimizerUnderTest; + private HandlerThread mHandlerThread; + private Handler mHandler; + private CountDownLatch mCountDown; + + @Rule + public TestableDeviceConfig.TestableDeviceConfigRule + mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule(); + + @Before + public void setUp() { + mHandlerThread = new HandlerThread(""); + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); + + mThread = new ServiceThread("TestServiceThread", Process.THREAD_PRIORITY_DEFAULT, + true /* allowIo */); + mThread.start(); + + ActivityManagerService ams = new ActivityManagerService( + new TestInjector(InstrumentationRegistry.getInstrumentation().getContext()), + mThread); + mCachedAppOptimizerUnderTest = new CachedAppOptimizer(ams, + new CachedAppOptimizer.PropertyChangedCallbackForTest() { + @Override + public void onPropertyChanged() { + if (mCountDown != null) { + mCountDown.countDown(); + } + } + }); + } + + @After + public void tearDown() { + mHandlerThread.quit(); + mThread.quit(); + mCountDown = null; + } + + @Test + public void init_setsDefaults() { + mCachedAppOptimizerUnderTest.init(); + assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isEqualTo( + CachedAppOptimizer.DEFAULT_USE_COMPACTION); + assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome).isEqualTo( + compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1)); + assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull).isEqualTo( + compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2)); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4); + assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo( + CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE); + assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6); + assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB); + assertThat(mCachedAppOptimizerUnderTest.mFullDeltaRssThrottleKb).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB); + + Set<Integer> expected = new HashSet<>(); + for (String s : TextUtils.split( + CachedAppOptimizer.DEFAULT_COMPACT_PROC_STATE_THROTTLE, ",")) { + expected.add(Integer.parseInt(s)); + } + assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle) + .containsExactlyElementsIn(expected); + } + + @Test + public void init_withDeviceConfigSetsParameters() { + // When the DeviceConfig already has a flag value stored (note this test will need to + // change if the default value changes from false). + assertThat(CachedAppOptimizer.DEFAULT_USE_COMPACTION).isFalse(); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_USE_COMPACTION, "true", false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_ACTION_1, + Integer.toString((CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1 + 1 % 4) + 1), false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_ACTION_2, + Integer.toString((CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2 + 1 % 4) + 1), false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_THROTTLE_1, + Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1 + 1), false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_THROTTLE_2, + Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2 + 1), false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_THROTTLE_3, + Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3 + 1), false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_THROTTLE_4, + Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4 + 1), false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_THROTTLE_5, + Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1), false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_THROTTLE_6, + Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1), false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_STATSD_SAMPLE_RATE, + Float.toString(CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE + 0.1f), false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_FULL_RSS_THROTTLE_KB, + Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1), false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, + Long.toString( + CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1), false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "1,2,3", false); + + // Then calling init will read and set that flag. + mCachedAppOptimizerUnderTest.init(); + assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isTrue(); + assertThat(mCachedAppOptimizerUnderTest.mCachedAppOptimizerThread.isAlive()).isTrue(); + + assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome).isEqualTo( + compactActionIntToString( + (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1 + 1 % 4) + 1)); + assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull).isEqualTo( + compactActionIntToString( + (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2 + 1 % 4) + 1)); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1 + 1); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2 + 1); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3 + 1); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4 + 1); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1); + assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo( + CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE + 0.1f); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1); + assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1); + assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle).containsExactly(1, 2, 3); + } + + @Test + public void useCompaction_listensToDeviceConfigChanges() throws InterruptedException { + assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isEqualTo( + CachedAppOptimizer.DEFAULT_USE_COMPACTION); + // When we call init and change some the flag value... + mCachedAppOptimizerUnderTest.init(); + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_USE_COMPACTION, "true", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + + // Then that new flag value is updated in the implementation. + assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isTrue(); + assertThat(mCachedAppOptimizerUnderTest.mCachedAppOptimizerThread.isAlive()).isTrue(); + + // And again, setting the flag the other way. + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_USE_COMPACTION, "false", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isFalse(); + } + + @Test + public void useCompaction_listensToDeviceConfigChangesBadValues() throws InterruptedException { + assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isEqualTo( + CachedAppOptimizer.DEFAULT_USE_COMPACTION); + mCachedAppOptimizerUnderTest.init(); + + // When we push an invalid flag value... + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_USE_COMPACTION, "foobar", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + + // Then we set the default. + assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isEqualTo( + CachedAppOptimizer.DEFAULT_USE_COMPACTION); + } + + @Test + public void compactAction_listensToDeviceConfigChanges() throws InterruptedException { + mCachedAppOptimizerUnderTest.init(); + + // When we override new values for the compaction action with reasonable values... + + // There are four possible values for compactAction[Some|Full]. + for (int i = 1; i < 5; i++) { + mCountDown = new CountDownLatch(2); + int expectedSome = (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1 + i) % 4 + 1; + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_ACTION_1, Integer.toString(expectedSome), false); + int expectedFull = (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2 + i) % 4 + 1; + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_ACTION_2, Integer.toString(expectedFull), false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + + // Then the updates are reflected in the flags. + assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome).isEqualTo( + compactActionIntToString(expectedSome)); + assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull).isEqualTo( + compactActionIntToString(expectedFull)); + } + } + + @Test + public void compactAction_listensToDeviceConfigChangesBadValues() throws InterruptedException { + mCachedAppOptimizerUnderTest.init(); + + // When we override new values for the compaction action with bad values ... + mCountDown = new CountDownLatch(2); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_ACTION_1, "foo", false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_ACTION_2, "foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + + // Then the default values are reflected in the flag + assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome).isEqualTo( + compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1)); + assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull).isEqualTo( + compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2)); + + mCountDown = new CountDownLatch(2); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_ACTION_1, "", false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_ACTION_2, "", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + + assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome).isEqualTo( + compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1)); + assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull).isEqualTo( + compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2)); + } + + @Test + public void compactThrottle_listensToDeviceConfigChanges() throws InterruptedException { + mCachedAppOptimizerUnderTest.init(); + + // When we override new reasonable throttle values after init... + mCountDown = new CountDownLatch(6); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_THROTTLE_1, + Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1 + 1), false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_THROTTLE_2, + Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2 + 1), false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_THROTTLE_3, + Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3 + 1), false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_THROTTLE_4, + Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4 + 1), false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_THROTTLE_5, + Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1), false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_THROTTLE_6, + Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1), false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + + // Then those flags values are reflected in the compactor. + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1 + 1); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2 + 1); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3 + 1); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4 + 1); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1); + } + + @Test + public void compactThrottle_listensToDeviceConfigChangesBadValues() + throws InterruptedException { + mCachedAppOptimizerUnderTest.init(); + + // When one of the throttles is overridden with a bad value... + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_THROTTLE_1, "foo", false); + // Then all the throttles have the defaults set. + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6); + + // Repeat for each of the throttle keys. + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_THROTTLE_2, "foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6); + + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_THROTTLE_3, "foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6); + + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_THROTTLE_4, "foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6); + + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_THROTTLE_5, "foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6); + + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_THROTTLE_6, "foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5); + assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6); + } + + @Test + public void statsdSampleRate_listensToDeviceConfigChanges() throws InterruptedException { + mCachedAppOptimizerUnderTest.init(); + + // When we override mStatsdSampleRate with a reasonable value ... + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_STATSD_SAMPLE_RATE, + Float.toString(CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE + 0.1f), false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + + // Then that override is reflected in the compactor. + assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo( + CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE + 0.1f); + } + + @Test + public void statsdSampleRate_listensToDeviceConfigChangesBadValues() + throws InterruptedException { + mCachedAppOptimizerUnderTest.init(); + + // When we override mStatsdSampleRate with an unreasonable value ... + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_STATSD_SAMPLE_RATE, "foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + + // Then that override is reflected in the compactor. + assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo( + CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE); + } + + @Test + public void statsdSampleRate_listensToDeviceConfigChangesOutOfRangeValues() + throws InterruptedException { + mCachedAppOptimizerUnderTest.init(); + + // When we override mStatsdSampleRate with an value outside of [0..1]... + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_STATSD_SAMPLE_RATE, + Float.toString(-1.0f), false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + + // Then the values is capped in the range. + assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo(0.0f); + + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_STATSD_SAMPLE_RATE, + Float.toString(1.01f), false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + + // Then the values is capped in the range. + assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo(1.0f); + } + + @Test + public void fullCompactionRssThrottleKb_listensToDeviceConfigChanges() + throws InterruptedException { + mCachedAppOptimizerUnderTest.init(); + + // When we override mStatsdSampleRate with a reasonable value ... + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_FULL_RSS_THROTTLE_KB, + Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1), false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + + // Then that override is reflected in the compactor. + assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1); + } + + @Test + public void fullCompactionRssThrottleKb_listensToDeviceConfigChangesBadValues() + throws InterruptedException { + mCachedAppOptimizerUnderTest.init(); + + // When we override mStatsdSampleRate with an unreasonable value ... + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + + // Then that override is reflected in the compactor. + assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB); + + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "-100", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + + // Then that override is reflected in the compactor. + assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB); + } + + @Test + public void fullCompactionDeltaRssThrottleKb_listensToDeviceConfigChanges() + throws InterruptedException { + mCachedAppOptimizerUnderTest.init(); + + // When we override mStatsdSampleRate with a reasonable value ... + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, + Long.toString( + CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1), false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + + // Then that override is reflected in the compactor. + assertThat(mCachedAppOptimizerUnderTest.mFullDeltaRssThrottleKb).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1); + } + + @Test + public void fullCompactionDeltaRssThrottleKb_listensToDeviceConfigChangesBadValues() + throws InterruptedException { + mCachedAppOptimizerUnderTest.init(); + + // When we override mStatsdSampleRate with an unreasonable value ... + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + + // Then that override is reflected in the compactor. + assertThat(mCachedAppOptimizerUnderTest.mFullDeltaRssThrottleKb).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB); + + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "-100", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + + // Then that override is reflected in the compactor. + assertThat(mCachedAppOptimizerUnderTest.mFullDeltaRssThrottleKb).isEqualTo( + CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB); + } + + @Test + public void procStateThrottle_listensToDeviceConfigChanges() + throws InterruptedException { + mCachedAppOptimizerUnderTest.init(); + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "1,2,3", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle).containsExactly(1, 2, 3); + + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle).isEmpty(); + } + + @Test + public void procStateThrottle_listensToDeviceConfigChangesBadValues() + throws InterruptedException { + mCachedAppOptimizerUnderTest.init(); + + Set<Integer> expected = new HashSet<>(); + for (String s : TextUtils.split( + CachedAppOptimizer.DEFAULT_COMPACT_PROC_STATE_THROTTLE, ",")) { + expected.add(Integer.parseInt(s)); + } + + // Not numbers + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle) + .containsExactlyElementsIn(expected); + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "1,foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle) + .containsExactlyElementsIn(expected); + + // Empty splits + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, ",", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle) + .containsExactlyElementsIn(expected); + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, ",,3", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle) + .containsExactlyElementsIn(expected); + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "1,,3", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle) + .containsExactlyElementsIn(expected); + } + + private class TestInjector extends Injector { + + TestInjector(Context context) { + super(context); + } + + @Override + public AppOpsService getAppOpsService(File file, Handler handler) { + return mAppOpsService; + } + + @Override + public Handler getUiHandler(ActivityManagerService service) { + return mHandler; + } + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java index f2e118d493f3..e0e374b2a83a 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java @@ -50,6 +50,7 @@ import android.net.NetworkPolicyManager; import android.os.BatteryManagerInternal; import android.os.Looper; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.SystemClock; import com.android.server.AppStateTracker; @@ -95,6 +96,7 @@ public class JobSchedulerServiceTest { .initMocks(this) .strictness(Strictness.LENIENT) .mockStatic(LocalServices.class) + .mockStatic(ServiceManager.class) .startMocking(); // Called in JobSchedulerService constructor. diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index ace15eb41261..556f6362d872 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -44,6 +44,7 @@ android_test { "servicestests-utils", "service-appsearch", "service-jobscheduler", + "service-permission", // TODO: remove once Android migrates to JUnit 4.12, // which provides assertThrows "testng", diff --git a/services/tests/servicestests/res/raw/comp_device_owner.xml b/services/tests/servicestests/res/raw/comp_device_owner.xml new file mode 100644 index 000000000000..0a10242ec59d --- /dev/null +++ b/services/tests/servicestests/res/raw/comp_device_owner.xml @@ -0,0 +1,8 @@ +<?xml version='1.0' encoding='utf-8' standalone='yes' ?> +<root> + <device-owner package="com.android.frameworks.servicestests" + name="" + component="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1" + userRestrictionsMigrated="true" /> + <device-owner-context userId="0" /> +</root> diff --git a/services/tests/servicestests/res/raw/comp_policies_primary.xml b/services/tests/servicestests/res/raw/comp_policies_primary.xml new file mode 100644 index 000000000000..1e1a0eff874c --- /dev/null +++ b/services/tests/servicestests/res/raw/comp_policies_primary.xml @@ -0,0 +1,7 @@ +<?xml version='1.0' encoding='utf-8' standalone='yes' ?> +<policies setup-complete="true" provisioning-state="3"> + <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1"> + <policies flags="991"/> + <password-history-length value="33" /> + </admin> +</policies> diff --git a/services/tests/servicestests/res/raw/comp_policies_profile_another_package.xml b/services/tests/servicestests/res/raw/comp_policies_profile_another_package.xml new file mode 100644 index 000000000000..141315e6c2d2 --- /dev/null +++ b/services/tests/servicestests/res/raw/comp_policies_profile_another_package.xml @@ -0,0 +1,6 @@ +<?xml version='1.0' encoding='utf-8' standalone='yes' ?> +<policies setup-complete="true" provisioning-state="3"> + <admin name="com.another.package.name/whatever.random.class"> + <policies flags="991"/> + </admin> +</policies> diff --git a/services/tests/servicestests/res/raw/comp_policies_profile_same_package.xml b/services/tests/servicestests/res/raw/comp_policies_profile_same_package.xml new file mode 100644 index 000000000000..c874dcca2c73 --- /dev/null +++ b/services/tests/servicestests/res/raw/comp_policies_profile_same_package.xml @@ -0,0 +1,6 @@ +<?xml version='1.0' encoding='utf-8' standalone='yes' ?> +<policies setup-complete="true" provisioning-state="3"> + <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1"> + <policies flags="991"/> + </admin> +</policies> diff --git a/services/tests/servicestests/res/raw/comp_profile_owner_another_package.xml b/services/tests/servicestests/res/raw/comp_profile_owner_another_package.xml new file mode 100644 index 000000000000..d65ba7826fef --- /dev/null +++ b/services/tests/servicestests/res/raw/comp_profile_owner_another_package.xml @@ -0,0 +1,7 @@ +<?xml version='1.0' encoding='utf-8' standalone='yes' ?> +<root> + <profile-owner package="com.another.package.name" + name="com.another.package.name" + component="com.another.package.name/whatever.random.class" + userRestrictionsMigrated="true"/> +</root> diff --git a/services/tests/servicestests/res/raw/comp_profile_owner_same_package.xml b/services/tests/servicestests/res/raw/comp_profile_owner_same_package.xml new file mode 100644 index 000000000000..7f98c91c0a94 --- /dev/null +++ b/services/tests/servicestests/res/raw/comp_profile_owner_same_package.xml @@ -0,0 +1,7 @@ +<?xml version='1.0' encoding='utf-8' standalone='yes' ?> +<root> + <profile-owner package="com.android.frameworks.servicestests" + name="com.android.frameworks.servicestests" + component="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1" + userRestrictionsMigrated="true"/> +</root> diff --git a/services/tests/servicestests/src/com/android/server/DynamicSystemServiceTest.java b/services/tests/servicestests/src/com/android/server/DynamicSystemServiceTest.java index 50437b4d5f3e..d367f71de921 100644 --- a/services/tests/servicestests/src/com/android/server/DynamicSystemServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/DynamicSystemServiceTest.java @@ -36,7 +36,7 @@ public class DynamicSystemServiceTest extends AndroidTestCase { public void test1() { assertTrue("dynamic_system service available", mService != null); try { - mService.startInstallation(); + mService.startInstallation("dsu"); fail("DynamicSystemService did not throw SecurityException as expected"); } catch (SecurityException e) { // expected diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java index 2fb2021de200..e609adc2a067 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java @@ -16,7 +16,7 @@ package com.android.server; -import static android.net.NetworkScoreManager.CACHE_FILTER_NONE; +import static android.net.NetworkScoreManager.SCORE_FILTER_NONE; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; @@ -306,7 +306,7 @@ public class NetworkScoreServiceTest { bindToScorer(true /*callerIsScorer*/); mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, - mNetworkScoreCache, CACHE_FILTER_NONE); + mNetworkScoreCache, SCORE_FILTER_NONE); mNetworkScoreService.updateScores(new ScoredNetwork[]{SCORED_NETWORK}); @@ -321,9 +321,9 @@ public class NetworkScoreServiceTest { bindToScorer(true /*callerIsScorer*/); mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, - mNetworkScoreCache, CACHE_FILTER_NONE); + mNetworkScoreCache, SCORE_FILTER_NONE); mNetworkScoreService.registerNetworkScoreCache( - NetworkKey.TYPE_WIFI, mNetworkScoreCache2, CACHE_FILTER_NONE); + NetworkKey.TYPE_WIFI, mNetworkScoreCache2, SCORE_FILTER_NONE); // updateScores should update both caches mNetworkScoreService.updateScores(new ScoredNetwork[]{SCORED_NETWORK}); @@ -378,7 +378,7 @@ public class NetworkScoreServiceTest { bindToScorer(true /*callerIsScorer*/); mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache, - CACHE_FILTER_NONE); + SCORE_FILTER_NONE); mNetworkScoreService.clearScores(); verify(mNetworkScoreCache).clearScores(); @@ -392,7 +392,7 @@ public class NetworkScoreServiceTest { .thenReturn(PackageManager.PERMISSION_GRANTED); mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache, - CACHE_FILTER_NONE); + SCORE_FILTER_NONE); mNetworkScoreService.clearScores(); verify(mNetworkScoreCache).clearScores(); @@ -472,7 +472,7 @@ public class NetworkScoreServiceTest { try { mNetworkScoreService.registerNetworkScoreCache( - NetworkKey.TYPE_WIFI, mNetworkScoreCache, CACHE_FILTER_NONE); + NetworkKey.TYPE_WIFI, mNetworkScoreCache, SCORE_FILTER_NONE); fail("SecurityException expected"); } catch (SecurityException e) { // expected @@ -615,7 +615,7 @@ public class NetworkScoreServiceTest { new ArrayList<>(scoredNetworkList), NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter); - consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_NONE); + consumer.accept(mNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_NONE); verify(mNetworkScoreCache).updateScores(scoredNetworkList); verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter); @@ -656,7 +656,7 @@ public class NetworkScoreServiceTest { Collections.emptyList(), NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter); - consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_NONE); + consumer.accept(mNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_NONE); verifyZeroInteractions(mNetworkScoreCache, mCurrentNetworkFilter, mScanResultsFilter); } @@ -673,7 +673,7 @@ public class NetworkScoreServiceTest { List<ScoredNetwork> filteredList = new ArrayList<>(scoredNetworkList); filteredList.remove(SCORED_NETWORK); when(mCurrentNetworkFilter.apply(scoredNetworkList)).thenReturn(filteredList); - consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK); + consumer.accept(mNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_CURRENT_NETWORK); verify(mNetworkScoreCache).updateScores(filteredList); verifyZeroInteractions(mScanResultsFilter); @@ -691,7 +691,7 @@ public class NetworkScoreServiceTest { List<ScoredNetwork> filteredList = new ArrayList<>(scoredNetworkList); filteredList.remove(SCORED_NETWORK); when(mScanResultsFilter.apply(scoredNetworkList)).thenReturn(filteredList); - consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS); + consumer.accept(mNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_SCAN_RESULTS); verify(mNetworkScoreCache).updateScores(filteredList); verifyZeroInteractions(mCurrentNetworkFilter); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java index c223f135d3df..e1e9b7e7b7cb 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java @@ -75,6 +75,7 @@ import android.os.IBinder; import android.os.IPowerManager; import android.os.PowerManager; import android.os.Process; +import android.os.RemoteCallback; import android.os.RemoteException; import android.testing.DexmakerShareClassLoaderRule; import android.view.Display; @@ -693,6 +694,18 @@ public class AbstractAccessibilityServiceConnectionTest { assertThat(result, is(false)); } + @Test + public void takeScreenshot_returnNull() { + // no canTakeScreenshot, should return null. + when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(false); + assertThat(mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY), is(nullValue())); + + // no checkAccessibilityAccess, should return null. + when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(true); + when(mMockSecurityPolicy.checkAccessibilityAccess(mServiceConnection)).thenReturn(false); + assertThat(mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY), is(nullValue())); + } + private void updateServiceInfo(AccessibilityServiceInfo serviceInfo, int eventType, int feedbackType, int flags, String[] packageNames, int notificationTimeout) { serviceInfo.eventTypes = eventType; @@ -832,5 +845,8 @@ public class AbstractAccessibilityServiceConnectionTest { @Override public void onFingerprintGesture(int gesture) {} + + @Override + public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {} } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java index 04ac7fe91008..150409766f47 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java @@ -346,6 +346,14 @@ public class AccessibilitySecurityPolicyTest { } @Test + public void canTakeScreenshot_hasCapability_returnTrue() { + when(mMockA11yServiceConnection.getCapabilities()) + .thenReturn(AccessibilityServiceInfo.CAPABILITY_CAN_TAKE_SCREENSHOT); + + assertTrue(mA11ySecurityPolicy.canTakeScreenshotLocked(mMockA11yServiceConnection)); + } + + @Test public void resolveProfileParent_userIdIsCurrentUser_returnCurrentUser() { final int currentUserId = 10; final int userId = currentUserId; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java index 96d9c476bcde..ac5169c7c715 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java @@ -113,7 +113,6 @@ public class AccessibilityUserStateTest { mUserState.mAccessibilityButtonTargets.add(COMPONENT_NAME.flattenToString()); mUserState.setTouchExplorationEnabledLocked(true); mUserState.setDisplayMagnificationEnabledLocked(true); - mUserState.setNavBarMagnificationEnabledLocked(true); mUserState.setAutoclickEnabledLocked(true); mUserState.setUserNonInteractiveUiTimeoutLocked(30); mUserState.setUserInteractiveUiTimeoutLocked(30); @@ -132,7 +131,6 @@ public class AccessibilityUserStateTest { assertTrue(mUserState.mAccessibilityButtonTargets.isEmpty()); assertFalse(mUserState.isTouchExplorationEnabledLocked()); assertFalse(mUserState.isDisplayMagnificationEnabledLocked()); - assertFalse(mUserState.isNavBarMagnificationEnabledLocked()); assertFalse(mUserState.isAutoclickEnabledLocked()); assertEquals(0, mUserState.getUserNonInteractiveUiTimeoutLocked()); assertEquals(0, mUserState.getUserInteractiveUiTimeoutLocked()); diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java index 0f11566f512c..39a3aae767ea 100644 --- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java @@ -212,7 +212,8 @@ public class AccountManagerServiceTest extends AndroidTestCase { String[] list = new String[]{AccountManagerServiceTestFixtures.CALLER_PACKAGE}; when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list); - Account[] accounts = mAms.getAccounts(null, mContext.getOpPackageName()); + Account[] accounts = mAms.getAccountsAsUser(null, + UserHandle.getCallingUserId(), mContext.getOpPackageName()); Arrays.sort(accounts, new AccountSorter()); assertEquals(6, accounts.length); assertEquals(a11, accounts[0]); @@ -222,8 +223,8 @@ public class AccountManagerServiceTest extends AndroidTestCase { assertEquals(a22, accounts[4]); assertEquals(a32, accounts[5]); - accounts = mAms.getAccounts(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, - mContext.getOpPackageName()); + accounts = mAms.getAccountsAsUser(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, + UserHandle.getCallingUserId(), mContext.getOpPackageName()); Arrays.sort(accounts, new AccountSorter()); assertEquals(3, accounts.length); assertEquals(a11, accounts[0]); @@ -232,8 +233,8 @@ public class AccountManagerServiceTest extends AndroidTestCase { mAms.removeAccountInternal(a21); - accounts = mAms.getAccounts(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, - mContext.getOpPackageName()); + accounts = mAms.getAccountsAsUser(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, + UserHandle.getCallingUserId(), mContext.getOpPackageName()); Arrays.sort(accounts, new AccountSorter()); assertEquals(2, accounts.length); assertEquals(a11, accounts[0]); @@ -373,7 +374,8 @@ public class AccountManagerServiceTest extends AndroidTestCase { unlockSystemUser(); String[] list = new String[]{AccountManagerServiceTestFixtures.CALLER_PACKAGE}; when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list); - Account[] accounts = mAms.getAccounts(null, mContext.getOpPackageName()); + Account[] accounts = mAms.getAccountsAsUser(null, UserHandle.getCallingUserId(), + mContext.getOpPackageName()); assertEquals("1 account should be migrated", 1, accounts.length); assertEquals(PreNTestDatabaseHelper.ACCOUNT_NAME, accounts[0].name); assertEquals(PreNTestDatabaseHelper.ACCOUNT_PASSWORD, mAms.getPassword(accounts[0])); @@ -2980,7 +2982,8 @@ public class AccountManagerServiceTest extends AndroidTestCase { Log.d(TAG, logPrefix + " getAccounts started"); long ti = System.currentTimeMillis(); try { - Account[] accounts = mAms.getAccounts(null, mContext.getOpPackageName()); + Account[] accounts = mAms.getAccountsAsUser(null, + UserHandle.getCallingUserId(), mContext.getOpPackageName()); if (accounts == null || accounts.length != 1 || !AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1.equals( accounts[0].type)) { @@ -3051,7 +3054,8 @@ public class AccountManagerServiceTest extends AndroidTestCase { Log.d(TAG, logPrefix + " getAccounts started"); long ti = System.currentTimeMillis(); try { - Account[] accounts = mAms.getAccounts(null, mContext.getOpPackageName()); + Account[] accounts = mAms.getAccountsAsUser(null, + UserHandle.getCallingUserId(), mContext.getOpPackageName()); if (accounts == null || accounts.length != 1 || !AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1.equals( accounts[0].type)) { diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index 79cc3db90fff..f1220146c5e5 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -31,6 +31,7 @@ import static com.android.server.am.UserController.USER_SWITCH_TIMEOUT_MSG; import static com.google.android.collect.Lists.newArrayList; import static com.google.android.collect.Sets.newHashSet; +import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertEquals; @@ -54,6 +55,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.annotation.UserIdInt; +import android.app.ActivityManager; import android.app.IUserSwitchObserver; import android.content.Context; import android.content.IIntentReceiver; @@ -74,10 +76,10 @@ import android.os.storage.IStorageManager; import android.platform.test.annotations.Presubmit; import android.util.Log; - import androidx.test.filters.SmallTest; import com.android.server.FgThread; +import com.android.server.am.UserState.KeyEvictedCallback; import com.android.server.pm.UserManagerService; import com.android.server.wm.WindowManagerService; @@ -107,6 +109,7 @@ public class UserControllerTest { private static final int TEST_USER_ID = 100; private static final int TEST_USER_ID1 = 101; private static final int TEST_USER_ID2 = 102; + private static final int TEST_USER_ID3 = 103; private static final int NONEXIST_USER_ID = 2; private static final int TEST_PRE_CREATED_USER_ID = 103; @@ -120,6 +123,8 @@ public class UserControllerTest { private TestInjector mInjector; private final HashMap<Integer, UserState> mUserStates = new HashMap<>(); + private final KeyEvictedCallback mKeyEvictedCallback = (userId) -> { /* ignore */ }; + private static final List<String> START_FOREGROUND_USER_ACTIONS = newArrayList( Intent.ACTION_USER_STARTED, Intent.ACTION_USER_SWITCHED, @@ -153,6 +158,7 @@ public class UserControllerTest { doNothing().when(mInjector).activityManagerOnUserStopped(anyInt()); doNothing().when(mInjector).clearBroadcastQueueForUser(anyInt()); doNothing().when(mInjector).stackSupervisorRemoveUser(anyInt()); + // All UserController params are set to default. mUserController = new UserController(mInjector); setUpUser(TEST_USER_ID, NO_USERINFO_FLAGS); setUpUser(TEST_PRE_CREATED_USER_ID, NO_USERINFO_FLAGS, /* preCreated=*/ true); @@ -187,7 +193,9 @@ public class UserControllerTest { @Test public void testStartUserUIDisabled() { - mUserController.mUserSwitchUiEnabled = false; + mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false, + /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); + mUserController.startUser(TEST_USER_ID, true /* foreground */); verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt()); verify(mInjector.getWindowManager(), never()).stopFreezingScreen(); @@ -245,7 +253,9 @@ public class UserControllerTest { @Test public void testFailedStartUserInForeground() { - mUserController.mUserSwitchUiEnabled = false; + mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false, + /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); + mUserController.startUserInForeground(NONEXIST_USER_ID); verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(anyBoolean()); verify(mInjector.getWindowManager()).setSwitchingUser(false); @@ -326,7 +336,9 @@ public class UserControllerTest { @Test public void testContinueUserSwitchUIDisabled() throws RemoteException { - mUserController.mUserSwitchUiEnabled = false; + mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false, + /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); + // Start user -- this will update state of mUserController mUserController.startUser(TEST_USER_ID, true); Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG); @@ -389,11 +401,13 @@ public class UserControllerTest { * Test stopping of user from max running users limit. */ @Test - public void testUserStoppingForMultipleUsersNormalMode() + public void testUserLockingFromUserSwitchingForMultipleUsersNonDelayedLocking() throws InterruptedException, RemoteException { + mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, + /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); + setUpUser(TEST_USER_ID1, 0); setUpUser(TEST_USER_ID2, 0); - mUserController.mMaxRunningUsers = 3; int numerOfUserSwitches = 1; addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM, numerOfUserSwitches, false); @@ -430,12 +444,13 @@ public class UserControllerTest { * all middle steps which takes too much work to mock. */ @Test - public void testUserStoppingForMultipleUsersDelayedLockingMode() + public void testUserLockingFromUserSwitchingForMultipleUsersDelayedLockingMode() throws InterruptedException, RemoteException { + mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, + /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true); + setUpUser(TEST_USER_ID1, 0); setUpUser(TEST_USER_ID2, 0); - mUserController.mMaxRunningUsers = 3; - mUserController.mDelayUserDataLocking = true; int numerOfUserSwitches = 1; addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM, numerOfUserSwitches, false); @@ -456,7 +471,7 @@ public class UserControllerTest { // Skip all other steps and test unlock delaying only UserState uss = mUserStates.get(TEST_USER_ID); uss.setState(UserState.STATE_SHUTDOWN); // necessary state change from skipped part - mUserController.finishUserStopped(uss); + mUserController.finishUserStopped(uss, /* allowDelayedLocking= */ true); // Cannot mock FgThread handler, so confirm that there is no posted message left before // checking. waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS); @@ -473,12 +488,87 @@ public class UserControllerTest { mUserController.getRunningUsersLU()); UserState ussUser1 = mUserStates.get(TEST_USER_ID1); ussUser1.setState(UserState.STATE_SHUTDOWN); - mUserController.finishUserStopped(ussUser1); + mUserController.finishUserStopped(ussUser1, /* allowDelayedLocking= */ true); waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS); verify(mInjector.mStorageManagerMock, times(1)) .lockUserKey(TEST_USER_ID); } + /** + * Test locking user with mDelayUserDataLocking false. + */ + @Test + public void testUserLockingWithStopUserForNonDelayedLockingMode() throws Exception { + mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, + /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); + + setUpAndStartUserInBackground(TEST_USER_ID); + assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* delayedLocking= */ true, + /* keyEvictedCallback= */ null, /* expectLocking= */ true); + + setUpAndStartUserInBackground(TEST_USER_ID1); + assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true, + /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true); + + setUpAndStartUserInBackground(TEST_USER_ID2); + assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* delayedLocking= */ false, + /* keyEvictedCallback= */ null, /* expectLocking= */ true); + + setUpAndStartUserInBackground(TEST_USER_ID3); + assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID3, /* delayedLocking= */ false, + /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true); + } + + /** + * Test conditional delayed locking with mDelayUserDataLocking true. + */ + @Test + public void testUserLockingForDelayedLockingMode() throws Exception { + mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, + /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true); + + // delayedLocking set and no KeyEvictedCallback, so it should not lock. + setUpAndStartUserInBackground(TEST_USER_ID); + assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* delayedLocking= */ true, + /* keyEvictedCallback= */ null, /* expectLocking= */ false); + + setUpAndStartUserInBackground(TEST_USER_ID1); + assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true, + /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true); + + setUpAndStartUserInBackground(TEST_USER_ID2); + assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* delayedLocking= */ false, + /* keyEvictedCallback= */ null, /* expectLocking= */ true); + + setUpAndStartUserInBackground(TEST_USER_ID3); + assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID3, /* delayedLocking= */ false, + /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true); + } + + private void setUpAndStartUserInBackground(int userId) throws Exception { + setUpUser(userId, 0); + mUserController.startUser(userId, /* foreground= */ false); + verify(mInjector.mStorageManagerMock, times(1)) + .unlockUserKey(TEST_USER_ID, 0, null, null); + mUserStates.put(userId, mUserController.getStartedUserState(userId)); + } + + private void assertUserLockedOrUnlockedAfterStopping(int userId, boolean delayedLocking, + KeyEvictedCallback keyEvictedCallback, boolean expectLocking) throws Exception { + int r = mUserController.stopUser(userId, /* force= */ true, /* delayedLocking= */ + delayedLocking, null, keyEvictedCallback); + assertThat(r).isEqualTo(ActivityManager.USER_OP_SUCCESS); + // fake all interim steps + UserState ussUser = mUserStates.get(userId); + ussUser.setState(UserState.STATE_SHUTDOWN); + // Passing delayedLocking invalidates incorrect internal data passing but currently there is + // no easy way to get that information passed through lambda. + mUserController.finishUserStopped(ussUser, delayedLocking); + waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS); + verify(mInjector.mStorageManagerMock, times(expectLocking ? 1 : 0)) + .lockUserKey(userId); + } + private void addForegroundUserAndContinueUserSwitch(int newUserId, int expectedOldUserId, int expectedNumberOfCalls, boolean expectOldUserStopping) throws RemoteException { diff --git a/services/tests/servicestests/src/com/android/server/appsearch/impl/AppSearchImplTest.java b/services/tests/servicestests/src/com/android/server/appsearch/impl/AppSearchImplTest.java new file mode 100644 index 000000000000..41956794aaf2 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/appsearch/impl/AppSearchImplTest.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.appsearch.impl; + +import static com.google.common.truth.Truth.assertThat; + +import static org.testng.Assert.expectThrows; + +import android.annotation.UserIdInt; +import android.content.Context; +import android.os.UserHandle; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.google.android.icing.proto.IndexingConfig; +import com.google.android.icing.proto.PropertyConfigProto; +import com.google.android.icing.proto.SchemaProto; +import com.google.android.icing.proto.SchemaTypeConfigProto; +import com.google.android.icing.proto.TermMatchType; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class AppSearchImplTest { + private final Context mContext = InstrumentationRegistry.getContext(); + private final @UserIdInt int mUserId = UserHandle.getCallingUserId(); + + @Test + public void testRewriteSchemaTypes() { + SchemaProto inSchema = SchemaProto.newBuilder() + .addTypes(SchemaTypeConfigProto.newBuilder() + .setSchemaType("TestType") + .addProperties(PropertyConfigProto.newBuilder() + .setPropertyName("subject") + .setDataType(PropertyConfigProto.DataType.Code.STRING) + .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL) + .setIndexingConfig( + IndexingConfig.newBuilder() + .setTokenizerType( + IndexingConfig.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() + ).build(); + + SchemaProto expectedSchema = SchemaProto.newBuilder() + .addTypes(SchemaTypeConfigProto.newBuilder() + .setSchemaType("com.android.server.appsearch.impl@42:TestType") + .addProperties(PropertyConfigProto.newBuilder() + .setPropertyName("subject") + .setDataType(PropertyConfigProto.DataType.Code.STRING) + .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL) + .setIndexingConfig( + IndexingConfig.newBuilder() + .setTokenizerType( + IndexingConfig.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("com.android.server.appsearch.impl@42:RefType") + .build() + ).build() + ).build(); + + AppSearchImpl impl = new AppSearchImpl(mContext, mUserId); + SchemaProto.Builder actualSchema = inSchema.toBuilder(); + impl.rewriteSchemaTypes("com.android.server.appsearch.impl@42:", actualSchema); + + assertThat(actualSchema.build()).isEqualTo(expectedSchema); + } + + @Test + public void testPackageNotFound() { + AppSearchImpl impl = new AppSearchImpl(mContext, mUserId); + IllegalStateException e = expectThrows( + IllegalStateException.class, + () -> impl.setSchema( + /*callingUid=*/Integer.MAX_VALUE, SchemaProto.getDefaultInstance())); + assertThat(e).hasMessageThat().contains("Failed to look up package name"); + } +} 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 f96d9961d364..bec265e6d62d 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java @@ -411,7 +411,8 @@ public class BiometricServiceTest { // HAT sent to keystore verify(mBiometricService.mKeyStore).addAuthToken(any(byte[].class)); // Send onAuthenticated to client - verify(mReceiver1).onAuthenticationSucceeded(); + verify(mReceiver1).onAuthenticationSucceeded( + BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC); // Current session becomes null assertNull(mBiometricService.mCurrentAuthSession); } @@ -461,7 +462,8 @@ public class BiometricServiceTest { BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED); waitForIdle(); verify(mBiometricService.mKeyStore).addAuthToken(any(byte[].class)); - verify(mReceiver1).onAuthenticationSucceeded(); + verify(mReceiver1).onAuthenticationSucceeded( + BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC); } @Test diff --git a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java index abe39f0934db..312ff2ca84a1 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java @@ -223,4 +223,22 @@ public class UtilsTest { Utils.biometricConstantsToBiometricManager(testCases[i][0])); } } + + @Test + public void testGetAuthenticationTypeForResult_getsCorrectType() { + assertEquals(Utils.getAuthenticationTypeForResult( + BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED), + BiometricPrompt.AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL); + assertEquals(Utils.getAuthenticationTypeForResult( + BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED), + BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC); + assertEquals(Utils.getAuthenticationTypeForResult( + BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED), + BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetAuthResultType_throwsForInvalidReason() { + Utils.getAuthenticationTypeForResult(BiometricPrompt.DISMISSED_REASON_NEGATIVE); + } } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java index 5f1f3083361b..46b83713c159 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java @@ -15,6 +15,10 @@ */ package com.android.server.devicepolicy; +import static android.os.UserHandle.USER_SYSTEM; + +import static com.android.server.devicepolicy.DpmTestUtils.writeInputStreamToFile; + import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; @@ -22,12 +26,14 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.when; import android.app.admin.DevicePolicyManagerInternal; +import android.content.ComponentName; import android.content.pm.PackageManager; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; +import com.android.frameworks.servicestests.R; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.devicepolicy.DevicePolicyManagerServiceTestable.OwnersTestable; @@ -37,9 +43,13 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; +// TODO (b/143516163): Fix old test cases and put into presubmit. public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { private static final String USER_TYPE_EMPTY = ""; + private static final int COPE_ADMIN1_APP_ID = 123; + private static final int COPE_ANOTHER_ADMIN_APP_ID = 125; + private static final int COPE_PROFILE_USER_ID = 11; private DpmMockContext mContext; @@ -85,7 +95,7 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { // Set up UserManager when(getServices().userManagerInternal.getBaseUserRestrictions( - eq(UserHandle.USER_SYSTEM))).thenReturn(DpmTestUtils.newRestrictions( + eq(USER_SYSTEM))).thenReturn(DpmTestUtils.newRestrictions( UserManager.DISALLOW_ADD_USER, UserManager.DISALLOW_RECORD_AUDIO)); @@ -137,7 +147,7 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { } assertTrue(dpms.mOwners.hasDeviceOwner()); - assertFalse(dpms.mOwners.hasProfileOwner(UserHandle.USER_SYSTEM)); + assertFalse(dpms.mOwners.hasProfileOwner(USER_SYSTEM)); assertTrue(dpms.mOwners.hasProfileOwner(10)); assertTrue(dpms.mOwners.hasProfileOwner(11)); assertFalse(dpms.mOwners.hasProfileOwner(12)); @@ -145,7 +155,7 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { // Now all information should be migrated. assertFalse(dpms.mOwners.getDeviceOwnerUserRestrictionsNeedsMigration()); assertFalse(dpms.mOwners.getProfileOwnerUserRestrictionsNeedsMigration( - UserHandle.USER_SYSTEM)); + USER_SYSTEM)); assertFalse(dpms.mOwners.getProfileOwnerUserRestrictionsNeedsMigration(10)); assertFalse(dpms.mOwners.getProfileOwnerUserRestrictionsNeedsMigration(11)); assertFalse(dpms.mOwners.getProfileOwnerUserRestrictionsNeedsMigration(12)); @@ -155,7 +165,7 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { DpmTestUtils.newRestrictions( UserManager.DISALLOW_RECORD_AUDIO ), - newBaseRestrictions.get(UserHandle.USER_SYSTEM)); + newBaseRestrictions.get(USER_SYSTEM)); DpmTestUtils.assertRestrictions( DpmTestUtils.newRestrictions( @@ -214,7 +224,7 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { // Set up UserManager when(getServices().userManagerInternal.getBaseUserRestrictions( - eq(UserHandle.USER_SYSTEM))).thenReturn(DpmTestUtils.newRestrictions( + eq(USER_SYSTEM))).thenReturn(DpmTestUtils.newRestrictions( UserManager.DISALLOW_ADD_USER, UserManager.DISALLOW_RECORD_AUDIO, UserManager.DISALLOW_SMS, @@ -249,19 +259,19 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { mContext.binder.restoreCallingIdentity(ident); } assertFalse(dpms.mOwners.hasDeviceOwner()); - assertTrue(dpms.mOwners.hasProfileOwner(UserHandle.USER_SYSTEM)); + assertTrue(dpms.mOwners.hasProfileOwner(USER_SYSTEM)); // Now all information should be migrated. assertFalse(dpms.mOwners.getDeviceOwnerUserRestrictionsNeedsMigration()); assertFalse(dpms.mOwners.getProfileOwnerUserRestrictionsNeedsMigration( - UserHandle.USER_SYSTEM)); + USER_SYSTEM)); // Check the new base restrictions. DpmTestUtils.assertRestrictions( DpmTestUtils.newRestrictions( UserManager.DISALLOW_RECORD_AUDIO ), - newBaseRestrictions.get(UserHandle.USER_SYSTEM)); + newBaseRestrictions.get(USER_SYSTEM)); // Check the new owner restrictions. DpmTestUtils.assertRestrictions( @@ -270,7 +280,7 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { UserManager.DISALLOW_SMS, UserManager.DISALLOW_OUTGOING_CALLS ), - dpms.getProfileOwnerAdminLocked(UserHandle.USER_SYSTEM).ensureUserRestrictions()); + dpms.getProfileOwnerAdminLocked(USER_SYSTEM).ensureUserRestrictions()); } // Test setting default restrictions for managed profile. @@ -332,4 +342,92 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { assertEquals(alreadySet.size(), 1); assertTrue(alreadySet.contains(UserManager.DISALLOW_BLUETOOTH_SHARING)); } + + public void testCompMigrationUnAffiliated_skipped() throws Exception { + prepareAdmin1AsDo(); + prepareAdminAnotherPackageAsPo(COPE_PROFILE_USER_ID); + + final DevicePolicyManagerServiceTestable dpms; + dpms = bootDpmsUp(); + + // DO should still be DO since no migration should happen. + assertTrue(dpms.mOwners.hasDeviceOwner()); + } + + public void testCompMigrationAffiliated() throws Exception { + prepareAdmin1AsDo(); + prepareAdmin1AsPo(COPE_PROFILE_USER_ID); + + // Secure lock screen is needed for password policy APIs to work. + when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(true); + + final DevicePolicyManagerServiceTestable dpms; + dpms = bootDpmsUp(); + + // DO should cease to be DO. + assertFalse(dpms.mOwners.hasDeviceOwner()); + + final DpmMockContext poContext = new DpmMockContext(getServices(), mRealTestContext); + poContext.binder.callingUid = UserHandle.getUid(COPE_PROFILE_USER_ID, COPE_ADMIN1_APP_ID); + + runAsCaller(poContext, dpms, dpm -> { + // Check that DO policy is now set on parent instance. + assertEquals(33, dpm.getParentProfileInstance(admin1).getPasswordHistoryLength(admin1)); + // And NOT set on profile instance. + assertEquals(0, dpm.getPasswordHistoryLength(admin1)); + + // TODO(b/143516163): verify more policies. + }); + } + + private DevicePolicyManagerServiceTestable bootDpmsUp() { + DevicePolicyManagerServiceTestable dpms; + final long ident = mContext.binder.clearCallingIdentity(); + try { + LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); + + dpms = new DevicePolicyManagerServiceTestable(getServices(), mContext); + + dpms.systemReady(SystemService.PHASE_LOCK_SETTINGS_READY); + dpms.systemReady(SystemService.PHASE_ACTIVITY_MANAGER_READY); + dpms.systemReady(SystemService.PHASE_BOOT_COMPLETED); + } finally { + mContext.binder.restoreCallingIdentity(ident); + } + return dpms; + } + + private void prepareAdmin1AsDo() throws Exception { + setUpPackageManagerForAdmin(admin1, UserHandle.getUid(USER_SYSTEM, COPE_ADMIN1_APP_ID)); + final int xmlResource = R.raw.comp_policies_primary; + writeInputStreamToFile(getRawStream(xmlResource), + (new File(getServices().systemUserDataDir, "device_policies.xml")) + .getAbsoluteFile()); + writeInputStreamToFile(getRawStream(R.raw.comp_device_owner), + (new File(getServices().dataDir, "device_owner_2.xml")) + .getAbsoluteFile()); + } + + private void prepareAdmin1AsPo(int profileUserId) throws Exception { + preparePo(profileUserId, admin1, R.raw.comp_profile_owner_same_package, + R.raw.comp_policies_profile_same_package, COPE_ADMIN1_APP_ID); + } + + private void prepareAdminAnotherPackageAsPo(int profileUserId) throws Exception { + preparePo(profileUserId, adminAnotherPackage, R.raw.comp_profile_owner_another_package, + R.raw.comp_policies_profile_another_package, COPE_ANOTHER_ADMIN_APP_ID); + } + + private void preparePo(int profileUserId, ComponentName admin, int profileOwnerXmlResId, + int policyXmlResId, int adminAppId) throws Exception { + final File profileDir = getServices().addUser(profileUserId, 0, + UserManager.USER_TYPE_PROFILE_MANAGED, USER_SYSTEM /* profile group */); + setUpPackageManagerForFakeAdmin( + admin, UserHandle.getUid(profileUserId, adminAppId), admin1); + writeInputStreamToFile(getRawStream(policyXmlResId), + (new File(profileDir, "device_policies.xml")).getAbsoluteFile()); + writeInputStreamToFile(getRawStream(profileOwnerXmlResId), + (new File(profileDir, "profile_owner.xml")).getAbsoluteFile()); + } + } 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 21034d3de9b4..bfadeea40034 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -34,6 +34,7 @@ import static com.android.server.testutils.TestUtils.assertExpectException; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.anyObject; @@ -57,7 +58,6 @@ import static org.mockito.hamcrest.MockitoHamcrest.argThat; import static org.testng.Assert.assertThrows; import android.Manifest.permission; -import android.annotation.RawRes; import android.app.Activity; import android.app.AppOpsManager; import android.app.Notification; @@ -111,7 +111,6 @@ import org.mockito.internal.util.collections.Sets; import org.mockito.stubbing.Answer; import java.io.File; -import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -275,6 +274,29 @@ public class DevicePolicyManagerTest extends DpmTestBase { }).when(getServices().userManager).getApplicationRestrictions( anyString(), any(UserHandle.class)); + // Emulate UserManager.setUserRestriction/getUserRestrictions + final Map<UserHandle, Bundle> userRestrictions = new HashMap<>(); + + doAnswer((Answer<Void>) invocation -> { + String key = (String) invocation.getArguments()[0]; + boolean value = (Boolean) invocation.getArguments()[1]; + UserHandle user = (UserHandle) invocation.getArguments()[2]; + Bundle userBundle = userRestrictions.getOrDefault(user, new Bundle()); + userBundle.putBoolean(key, value); + + userRestrictions.put(user, userBundle); + return null; + }).when(getServices().userManager).setUserRestriction( + anyString(), anyBoolean(), any(UserHandle.class)); + + doAnswer((Answer<Boolean>) invocation -> { + String key = (String) invocation.getArguments()[0]; + UserHandle user = (UserHandle) invocation.getArguments()[1]; + Bundle userBundle = userRestrictions.getOrDefault(user, new Bundle()); + return userBundle.getBoolean(key); + }).when(getServices().userManager).hasUserRestriction( + anyString(), any(UserHandle.class)); + // Add the first secondary user. getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, 0, UserManager.USER_TYPE_FULL_SECONDARY); @@ -822,10 +844,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, DpmMockContext.SYSTEM_UID); - // Setup device owner. mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; mContext.packageName = admin1.getPackageName(); - setupDeviceOwner(); // Add a managed profile belonging to the system user. addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1); @@ -833,18 +853,13 @@ public class DevicePolicyManagerTest extends DpmTestBase { // Change the parent user's password. dpm.reportPasswordChanged(UserHandle.USER_SYSTEM); - // Both the device owner and the managed profile owner should receive this broadcast. + // The managed profile owner should receive this broadcast. final Intent intent = new Intent(DeviceAdminReceiver.ACTION_PASSWORD_CHANGED); intent.setComponent(admin1); intent.putExtra(Intent.EXTRA_USER, UserHandle.of(UserHandle.USER_SYSTEM)); verify(mContext.spiedContext, times(1)).sendBroadcastAsUser( MockUtils.checkIntent(intent), - MockUtils.checkUserHandle(UserHandle.USER_SYSTEM), - eq(null), - any(Bundle.class)); - verify(mContext.spiedContext, times(1)).sendBroadcastAsUser( - MockUtils.checkIntent(intent), MockUtils.checkUserHandle(MANAGED_PROFILE_USER_ID), eq(null), any(Bundle.class)); @@ -864,12 +879,11 @@ public class DevicePolicyManagerTest extends DpmTestBase { final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, DpmMockContext.SYSTEM_UID); - // Setup device owner. + // Configure system as having separate profile challenge. mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; mContext.packageName = admin1.getPackageName(); doReturn(true).when(getServices().lockPatternUtils) .isSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID); - setupDeviceOwner(); // Add a managed profile belonging to the system user. addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1); @@ -954,6 +968,10 @@ public class DevicePolicyManagerTest extends DpmTestBase { verify(getServices().iactivityManager, times(1)).updateDeviceOwner( eq(admin1.getPackageName())); + verify(getServices().userManager, times(1)).setUserRestriction( + eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE), + eq(true), eq(UserHandle.SYSTEM)); + verify(mContext.spiedContext, times(1)).sendBroadcastAsUser( MockUtils.checkIntentAction(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED), MockUtils.checkUserHandle(UserHandle.USER_SYSTEM)); @@ -1948,6 +1966,29 @@ public class DevicePolicyManagerTest extends DpmTestBase { // TODO Make sure restrictions are written to the file. } + private static final Set<String> PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS = + Sets.newSet( + UserManager.DISALLOW_CONFIG_DATE_TIME, + UserManager.DISALLOW_ADD_USER, + UserManager.DISALLOW_BLUETOOTH, + UserManager.DISALLOW_BLUETOOTH_SHARING, + UserManager.DISALLOW_CONFIG_BLUETOOTH, + UserManager.DISALLOW_CONFIG_CELL_BROADCASTS, + UserManager.DISALLOW_CONFIG_LOCATION, + UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, + UserManager.DISALLOW_CONFIG_PRIVATE_DNS, + UserManager.DISALLOW_CONFIG_TETHERING, + UserManager.DISALLOW_CONFIG_WIFI, + UserManager.DISALLOW_CONTENT_CAPTURE, + UserManager.DISALLOW_CONTENT_SUGGESTIONS, + UserManager.DISALLOW_DATA_ROAMING, + UserManager.DISALLOW_DEBUGGING_FEATURES, + UserManager.DISALLOW_SAFE_BOOT, + UserManager.DISALLOW_SHARE_LOCATION, + UserManager.DISALLOW_SMS, + UserManager.DISALLOW_USB_FILE_TRANSFER + ); + public void testSetUserRestriction_asPoOfOrgOwnedDevice() throws Exception { final int MANAGED_PROFILE_USER_ID = DpmMockContext.CALLER_USER_HANDLE; final int MANAGED_PROFILE_ADMIN_UID = @@ -1960,15 +2001,9 @@ public class DevicePolicyManagerTest extends DpmTestBase { when(getServices().userManager.getProfileParent(MANAGED_PROFILE_USER_ID)) .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0)); - parentDpm.addUserRestriction(admin1, UserManager.DISALLOW_CONFIG_DATE_TIME); - verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions( - eq(MANAGED_PROFILE_USER_ID), - MockUtils.checkUserRestrictions(UserManager.DISALLOW_CONFIG_DATE_TIME), - eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE)); - reset(getServices().userManagerInternal); - - parentDpm.clearUserRestriction(admin1, UserManager.DISALLOW_CONFIG_DATE_TIME); - reset(getServices().userManagerInternal); + for (String restriction : PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS) { + addAndRemoveUserRestrictionOnParentDpm(restriction); + } parentDpm.setCameraDisabled(admin1, true); verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions( @@ -1985,6 +2020,20 @@ public class DevicePolicyManagerTest extends DpmTestBase { reset(getServices().userManagerInternal); } + private void addAndRemoveUserRestrictionOnParentDpm(String restriction) { + parentDpm.addUserRestriction(admin1, restriction); + verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions( + eq(DpmMockContext.CALLER_USER_HANDLE), + MockUtils.checkUserRestrictions(restriction), + eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE)); + parentDpm.clearUserRestriction(admin1, restriction); + DpmTestUtils.assertRestrictions( + DpmTestUtils.newRestrictions(), + parentDpm.getUserRestrictions(admin1) + ); + reset(getServices().userManagerInternal); + } + public void testNoDefaultEnabledUserRestrictions() throws Exception { mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS); mContext.callerPermissions.add(permission.MANAGE_USERS); @@ -2005,12 +2054,11 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertNoDeviceOwnerRestrictions(); - // Initialize DPMS again and check that the user restriction wasn't enabled again. reset(getServices().userManagerInternal); - initializeDpms(); - assertTrue(dpm.isDeviceOwnerApp(admin1.getPackageName())); - assertNotNull(dpms.getDeviceOwnerAdminLocked()); + // Ensure the DISALLOW_REMOVE_MANAGED_PROFILES restriction doesn't show up as a + // restriction to the device owner. + dpm.addUserRestriction(admin1, UserManager.DISALLOW_REMOVE_MANAGED_PROFILE); assertNoDeviceOwnerRestrictions(); } @@ -3106,7 +3154,6 @@ public class DevicePolicyManagerTest extends DpmTestBase { setup_nonSplitUser_withDo_primaryUser(); final int MANAGED_PROFILE_USER_ID = 18; final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 1308); - addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1); when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, false /* we can't remove a managed profile */)).thenReturn(false); when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, @@ -3158,41 +3205,16 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, false); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, false); - // COMP mode is allowed. + // COMP mode NOT is allowed. assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, - DevicePolicyManager.CODE_OK); - assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); - - // And other DPCs can also provision a managed profile (DO + BYOD case). - assertCheckProvisioningPreCondition( - DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, - DpmMockContext.ANOTHER_PACKAGE_NAME, - DevicePolicyManager.CODE_OK); - assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true, - DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID); - } + DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); - public void testProvisioning_nonSplitUser_withDo_primaryUser_restrictedByDo() throws Exception { - setup_nonSplitUser_withDo_primaryUser(); - mContext.packageName = admin1.getPackageName(); - mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); - // The DO should be allowed to initiate provisioning if it set the restriction itself, but - // other packages should be forbidden. - when(getServices().userManager.hasUserRestriction( - eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE), - eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid)))) - .thenReturn(true); - when(getServices().userManager.getUserRestrictionSource( - eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE), - eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid)))) - .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER); - assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, - DevicePolicyManager.CODE_OK); - assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); + // And other DPCs can NOT provision a managed profile. assertCheckProvisioningPreCondition( DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, DpmMockContext.ANOTHER_PACKAGE_NAME, - DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED); + DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false, DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID); } @@ -3213,31 +3235,46 @@ public class DevicePolicyManagerTest extends DpmTestBase { eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid)))) .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, - DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED); + DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); assertCheckProvisioningPreCondition( DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, DpmMockContext.ANOTHER_PACKAGE_NAME, - DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED); + DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false, DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID); } - public void testCheckProvisioningPreCondition_nonSplitUser_comp() throws Exception { + public void testCheckCannotSetProfileOwnerWithDeviceOwner() throws Exception { + setup_nonSplitUser_withDo_primaryUser(); + final int managedProfileUserId = 18; + final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 1308); + + final int userId = UserHandle.getUserId(managedProfileAdminUid); + getServices().addUser(userId, 0, UserManager.USER_TYPE_PROFILE_MANAGED, + UserHandle.USER_SYSTEM); + mContext.callerPermissions.addAll(OWNER_SETUP_PERMISSIONS); + setUpPackageManagerForFakeAdmin(admin1, managedProfileAdminUid, admin1); + dpm.setActiveAdmin(admin1, false, userId); + assertFalse(dpm.setProfileOwner(admin1, null, userId)); + mContext.callerPermissions.removeAll(OWNER_SETUP_PERMISSIONS); + } + + public void testCheckProvisioningPreCondition_nonSplitUser_attemptingComp() throws Exception { setup_nonSplitUser_withDo_primaryUser_ManagedProfile(); mContext.packageName = admin1.getPackageName(); mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); // We can delete the managed profile to create a new one, so provisioning is allowed. assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, - DevicePolicyManager.CODE_OK); - assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); + DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); assertCheckProvisioningPreCondition( DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, DpmMockContext.ANOTHER_PACKAGE_NAME, - DevicePolicyManager.CODE_OK); - assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true, + DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false, DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID); } @@ -3265,8 +3302,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { // But the device owner can still do it because it has set the restriction itself. assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, - DevicePolicyManager.CODE_OK); - assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); + DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); } private void setup_splitUser_firstBoot_systemUser() throws Exception { @@ -3475,6 +3512,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { when(getServices().ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)) .thenReturn(true); when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true); + when(getServices().userManager.getProfileParent(DpmMockContext.CALLER_USER_HANDLE)) + .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0)); when(getServices().userManager.canAddMoreManagedProfiles(DpmMockContext.CALLER_USER_HANDLE, true)).thenReturn(true); setUserSetupCompleteForUser(false, DpmMockContext.CALLER_USER_HANDLE); @@ -3487,7 +3526,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { setup_provisionManagedProfileWithDeviceOwner_primaryUser(); setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); mContext.packageName = admin1.getPackageName(); - assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); } public void testCheckProvisioningPreCondition_provisionManagedProfileWithDeviceOwner_primaryUser() @@ -3495,9 +3534,9 @@ public class DevicePolicyManagerTest extends DpmTestBase { setup_provisionManagedProfileWithDeviceOwner_primaryUser(); mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); - // COMP mode is allowed. + // COMP mode is NOT allowed. assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, - DevicePolicyManager.CODE_OK); + DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE); } private void setup_provisionManagedProfileCantRemoveUser_primaryUser() throws Exception { @@ -3791,11 +3830,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { when(getServices().userManager.getUsers()) .thenReturn(Arrays.asList(managedProfileUserInfo)); - // Any caller without the MANAGE_USERS permission should get a security exception. - assertExpectException(SecurityException.class, null, () -> - dpm.isOrganizationOwnedDeviceWithManagedProfile()); - // But when the right permission is granted, this should succeed. - mContext.permissions.add(android.Manifest.permission.MANAGE_USERS); + // Any caller should be able to call this method. assertFalse(dpm.isOrganizationOwnedDeviceWithManagedProfile()); configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE); assertTrue(dpm.isOrganizationOwnedDeviceWithManagedProfile()); @@ -4014,11 +4049,6 @@ public class DevicePolicyManagerTest extends DpmTestBase { List<UserHandle> targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1); MoreAsserts.assertEmpty(targetUsers); - // Setup a managed profile managed by the same admin. - final int MANAGED_PROFILE_USER_ID = 15; - final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 20456); - addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1); - // Add a secondary user, it should never talk with. final int ANOTHER_USER_ID = 36; getServices().addUser(ANOTHER_USER_ID, 0, UserManager.USER_TYPE_FULL_SECONDARY); @@ -4028,30 +4058,11 @@ public class DevicePolicyManagerTest extends DpmTestBase { targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1); MoreAsserts.assertEmpty(targetUsers); - mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; - targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1); - MoreAsserts.assertEmpty(targetUsers); - // Setting affiliation ids final Set<String> userAffiliationIds = Collections.singleton("some.affiliation-id"); mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; dpm.setAffiliationIds(admin1, userAffiliationIds); - mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; - dpm.setAffiliationIds(admin1, userAffiliationIds); - - // Calling from device owner admin, the result list should just contain the managed - // profile user id. - mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; - targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1); - MoreAsserts.assertContentsInAnyOrder(targetUsers, UserHandle.of(MANAGED_PROFILE_USER_ID)); - - // Calling from managed profile admin, the result list should just contain the system - // user id. - mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; - targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1); - MoreAsserts.assertContentsInAnyOrder(targetUsers, UserHandle.SYSTEM); - // Changing affiliation ids in one dpm.setAffiliationIds(admin1, Collections.singleton("some-different-affiliation-id")); @@ -4065,38 +4076,6 @@ public class DevicePolicyManagerTest extends DpmTestBase { MoreAsserts.assertEmpty(targetUsers); } - public void testGetBindDeviceAdminTargetUsers_differentPackage() throws Exception { - // Setup a device owner. - mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; - setupDeviceOwner(); - - // Set up a managed profile managed by different package. - final int MANAGED_PROFILE_USER_ID = 15; - final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 20456); - final ComponentName adminDifferentPackage = - new ComponentName("another.package", "whatever.class"); - addManagedProfile(adminDifferentPackage, MANAGED_PROFILE_ADMIN_UID, admin2); - - // Setting affiliation ids - final Set<String> userAffiliationIds = Collections.singleton("some-affiliation-id"); - dpm.setAffiliationIds(admin1, userAffiliationIds); - - mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; - dpm.setAffiliationIds(adminDifferentPackage, userAffiliationIds); - - // Calling from device owner admin, we should get zero bind device admin target users as - // their packages are different. - mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; - List<UserHandle> targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1); - MoreAsserts.assertEmpty(targetUsers); - - // Calling from managed profile admin, we should still get zero target users for the same - // reason. - mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; - targetUsers = dpm.getBindDeviceAdminTargetUsers(adminDifferentPackage); - MoreAsserts.assertEmpty(targetUsers); - } - private void verifyLockTaskState(int userId) throws Exception { verifyLockTaskState(userId, new String[0], DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS); @@ -4133,79 +4112,6 @@ public class DevicePolicyManagerTest extends DpmTestBase { () -> dpm.setLockTaskFeatures(who, flags)); } - public void testLockTaskPolicyAllowedForAffiliatedUsers() throws Exception { - // Setup a device owner. - mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; - setupDeviceOwner(); - // Lock task policy is updated when loading user data. - verifyLockTaskState(UserHandle.USER_SYSTEM); - - // Set up a managed profile managed by different package (package name shouldn't matter) - final int MANAGED_PROFILE_USER_ID = 15; - final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 20456); - final ComponentName adminDifferentPackage = - new ComponentName("another.package", "whatever.class"); - addManagedProfile(adminDifferentPackage, MANAGED_PROFILE_ADMIN_UID, admin2); - verifyLockTaskState(MANAGED_PROFILE_USER_ID); - - // Setup a PO on the secondary user - mContext.binder.callingUid = DpmMockContext.CALLER_UID; - setAsProfileOwner(admin3); - verifyLockTaskState(DpmMockContext.CALLER_USER_HANDLE); - - // The DO can still set lock task packages - final String[] doPackages = {"doPackage1", "doPackage2"}; - final int flags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS - | DevicePolicyManager.LOCK_TASK_FEATURE_HOME - | DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW; - verifyCanSetLockTask(DpmMockContext.CALLER_SYSTEM_USER_UID, UserHandle.USER_SYSTEM, admin1, doPackages, flags); - - final String[] secondaryPoPackages = {"secondaryPoPackage1", "secondaryPoPackage2"}; - final int secondaryPoFlags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS - | DevicePolicyManager.LOCK_TASK_FEATURE_HOME - | DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW; - verifyCanNotSetLockTask(DpmMockContext.CALLER_UID, admin3, secondaryPoPackages, secondaryPoFlags); - - // Managed profile is unaffiliated - shouldn't be able to setLockTaskPackages. - mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; - final String[] poPackages = {"poPackage1", "poPackage2"}; - final int poFlags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS - | DevicePolicyManager.LOCK_TASK_FEATURE_HOME - | DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW; - verifyCanNotSetLockTask(MANAGED_PROFILE_ADMIN_UID, adminDifferentPackage, poPackages, poFlags); - - // Setting same affiliation ids - final Set<String> userAffiliationIds = Collections.singleton("some-affiliation-id"); - mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; - dpm.setAffiliationIds(admin1, userAffiliationIds); - - mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; - dpm.setAffiliationIds(adminDifferentPackage, userAffiliationIds); - - // Now the managed profile can set lock task packages. - dpm.setLockTaskPackages(adminDifferentPackage, poPackages); - MoreAsserts.assertEquals(poPackages, dpm.getLockTaskPackages(adminDifferentPackage)); - assertTrue(dpm.isLockTaskPermitted("poPackage1")); - assertFalse(dpm.isLockTaskPermitted("doPackage2")); - // And it can set lock task features. - dpm.setLockTaskFeatures(adminDifferentPackage, poFlags); - verifyLockTaskState(MANAGED_PROFILE_USER_ID, poPackages, poFlags); - - // Unaffiliate the profile, lock task mode no longer available on the profile. - dpm.setAffiliationIds(adminDifferentPackage, Collections.emptySet()); - assertFalse(dpm.isLockTaskPermitted("poPackage1")); - // Lock task packages cleared when loading user data and when the user becomes unaffiliated. - verify(getServices().iactivityManager, times(2)).updateLockTaskPackages( - MANAGED_PROFILE_USER_ID, new String[0]); - verify(getServices().iactivityTaskManager, times(2)).updateLockTaskFeatures( - MANAGED_PROFILE_USER_ID, DevicePolicyManager.LOCK_TASK_FEATURE_NONE); - - // Verify that lock task packages were not cleared for the DO - mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; - assertTrue(dpm.isLockTaskPermitted("doPackage1")); - - } - public void testLockTaskPolicyForProfileOwner() throws Exception { // Setup a PO mContext.binder.callingUid = DpmMockContext.CALLER_UID; @@ -5902,10 +5808,6 @@ public class DevicePolicyManagerTest extends DpmTestBase { return new File(parentDir, "device_policies.xml"); } - private InputStream getRawStream(@RawRes int id) { - return mRealTestContext.getResources().openRawResource(id); - } - private void setUserSetupCompleteForUser(boolean isUserSetupComplete, int userhandle) { when(getServices().settings.settingsSecureGetIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0, userhandle)).thenReturn(isUserSetupComplete ? 1 : 0); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java index a34c2ff8ce07..9a1a5fbfd186 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java @@ -24,6 +24,7 @@ import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.when; +import android.annotation.RawRes; import android.app.admin.DevicePolicyManager; import android.content.ComponentName; import android.content.Context; @@ -36,6 +37,7 @@ import android.content.pm.ResolveInfo; import android.os.UserHandle; import android.test.AndroidTestCase; +import java.io.InputStream; import java.util.List; public abstract class DpmTestBase extends AndroidTestCase { @@ -256,4 +258,8 @@ public abstract class DpmTestBase extends AndroidTestCase { invocation -> invocation.getArguments()[1] ); } + + protected InputStream getRawStream(@RawRes int id) { + return mRealTestContext.getResources().openRawResource(id); + } } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java index 6c2c1446a459..068daf5ee310 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java @@ -15,6 +15,7 @@ */ package com.android.server.devicepolicy; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; @@ -236,6 +237,13 @@ public class MockSystemServices { return ui == null ? null : getUserInfo(ui.profileGroupId); } ); + when(userManager.getProfileParent(any(UserHandle.class))).thenAnswer( + invocation -> { + final UserHandle userHandle = (UserHandle) invocation.getArguments()[0]; + final UserInfo ui = getUserInfo(userHandle.getIdentifier()); + return ui == null ? UserHandle.USER_NULL : UserHandle.of(ui.profileGroupId); + } + ); when(userManager.getProfiles(anyInt())).thenAnswer( invocation -> { final int userId12 = (int) invocation.getArguments()[0]; diff --git a/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java index dd69c6613ab2..a1810b971b09 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java @@ -16,6 +16,8 @@ package com.android.server.integrity; +import static com.android.server.integrity.model.IndexingFileConstants.INDEXING_BLOCK_SIZE; + import static com.google.common.truth.Truth.assertThat; import android.content.integrity.AppInstallMetadata; @@ -135,14 +137,15 @@ public class IntegrityFileManagerTest { Arrays.asList(packageNameRule, packageCertRule, versionCodeRule, randomRule); mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, rules); - AppInstallMetadata appInstallMetadata = new AppInstallMetadata.Builder() - .setPackageName(packageName) - .setAppCertificate(packageCert) - .setVersionCode(version) - .setInstallerName("abc") - .setInstallerCertificate("abc") - .setIsPreInstalled(true) - .build(); + AppInstallMetadata appInstallMetadata = + new AppInstallMetadata.Builder() + .setPackageName(packageName) + .setAppCertificate(packageCert) + .setVersionCode(version) + .setInstallerName("abc") + .setInstallerCertificate("abc") + .setIsPreInstalled(true) + .build(); List<Rule> rulesFetched = mIntegrityFileManager.readRules(appInstallMetadata); assertThat(rulesFetched) @@ -158,13 +161,14 @@ public class IntegrityFileManagerTest { // Create a rule set with 2500 package name indexed, 2500 app certificate indexed and // 500 unindexed rules. List<Rule> rules = new ArrayList<>(); + int unindexedRuleCount = 70; for (int i = 0; i < 2500; i++) { rules.add(getPackageNameIndexedRule(String.format("%s%04d", packageName, i))); rules.add(getAppCertificateIndexedRule(String.format("%s%04d", appCertificate, i))); } - for (int i = 0; i < 70; i++) { + for (int i = 0; i < unindexedRuleCount; i++) { rules.add(getInstallerCertificateRule(String.format("%s%04d", installerName, i))); } @@ -174,18 +178,20 @@ public class IntegrityFileManagerTest { // Read the rules for a specific rule. String installedPackageName = String.format("%s%04d", packageName, 264); String installedAppCertificate = String.format("%s%04d", appCertificate, 1264); - AppInstallMetadata appInstallMetadata = new AppInstallMetadata.Builder() - .setPackageName(installedPackageName) - .setAppCertificate(installedAppCertificate) - .setVersionCode(250) - .setInstallerName("abc") - .setInstallerCertificate("abc") - .setIsPreInstalled(true) - .build(); + AppInstallMetadata appInstallMetadata = + new AppInstallMetadata.Builder() + .setPackageName(installedPackageName) + .setAppCertificate(installedAppCertificate) + .setVersionCode(250) + .setInstallerName("abc") + .setInstallerCertificate("abc") + .setIsPreInstalled(true) + .build(); List<Rule> rulesFetched = mIntegrityFileManager.readRules(appInstallMetadata); // Verify that we do not load all the rules and we have the necessary rules to evaluate. - assertThat(rulesFetched.size()).isEqualTo(170); + assertThat(rulesFetched.size()) + .isEqualTo(INDEXING_BLOCK_SIZE * 2 + unindexedRuleCount); assertThat(rulesFetched) .containsAllOf( getPackageNameIndexedRule(installedPackageName), @@ -195,27 +201,38 @@ public class IntegrityFileManagerTest { private Rule getPackageNameIndexedRule(String packageName) { return new Rule( new StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - packageName, - /* isHashedValue= */ false), + AtomicFormula.PACKAGE_NAME, packageName, /* isHashedValue= */ false), Rule.DENY); } private Rule getAppCertificateIndexedRule(String appCertificate) { return new Rule( new StringAtomicFormula( - AtomicFormula.APP_CERTIFICATE, - appCertificate, - /* isHashedValue= */ false), + AtomicFormula.APP_CERTIFICATE, appCertificate, /* isHashedValue= */ false), Rule.DENY); } private Rule getInstallerCertificateRule(String installerCert) { return new Rule( new StringAtomicFormula( - AtomicFormula.INSTALLER_NAME, - installerCert, - /* isHashedValue= */ false), + AtomicFormula.INSTALLER_NAME, installerCert, /* isHashedValue= */ false), Rule.DENY); } + + @Test + public void testStagingDirectoryCleared() throws Exception { + // We must push rules two times to ensure that staging directory is empty because we cleared + // it, rather than because original rules directory is empty. + mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST); + mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST); + + assertStagingDirectoryCleared(); + } + + private void assertStagingDirectoryCleared() { + File stagingDir = new File(mTmpDir, "integrity_staging"); + assertThat(stagingDir.exists()).isTrue(); + assertThat(stagingDir.isDirectory()).isTrue(); + assertThat(stagingDir.listFiles()).isEmpty(); + } } diff --git a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java index 7a070ee72b5d..eda2b701fd8d 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java @@ -188,7 +188,7 @@ public class RuleEvaluatorTest { } @Test - public void testEvaluateRules_ruleNotInDNF_ignoreAndAllow() { + public void testEvaluateRules_orRules() { CompoundFormula compoundFormula = new CompoundFormula( CompoundFormula.OR, @@ -206,11 +206,11 @@ public class RuleEvaluatorTest { IntegrityCheckResult result = RuleEvaluator.evaluateRules(Collections.singletonList(rule), APP_INSTALL_METADATA); - assertEquals(ALLOW, result.getEffect()); + assertEquals(DENY, result.getEffect()); } @Test - public void testEvaluateRules_compoundFormulaWithNot_allow() { + public void testEvaluateRules_compoundFormulaWithNot_deny() { CompoundFormula openSubFormula = new CompoundFormula( CompoundFormula.AND, @@ -230,7 +230,7 @@ public class RuleEvaluatorTest { IntegrityCheckResult result = RuleEvaluator.evaluateRules(Collections.singletonList(rule), APP_INSTALL_METADATA); - assertEquals(ALLOW, result.getEffect()); + assertEquals(DENY, result.getEffect()); } @Test diff --git a/services/tests/servicestests/src/com/android/server/integrity/model/BitTrackedInputStreamTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/BitTrackedInputStreamTest.java deleted file mode 100644 index 1eb5eb51504a..000000000000 --- a/services/tests/servicestests/src/com/android/server/integrity/model/BitTrackedInputStreamTest.java +++ /dev/null @@ -1,142 +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 com.android.server.integrity.model; - -import static com.android.server.integrity.model.ComponentBitSize.ATOMIC_FORMULA_START; -import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_END; -import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_START; -import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS; -import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS; -import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS; -import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS; -import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS; -import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS; -import static com.android.server.integrity.utils.TestUtils.getBits; -import static com.android.server.integrity.utils.TestUtils.getBytes; -import static com.android.server.integrity.utils.TestUtils.getValueBits; - -import static com.google.common.truth.Truth.assertThat; - -import static org.testng.Assert.assertThrows; - -import android.content.integrity.AtomicFormula; -import android.content.integrity.CompoundFormula; -import android.content.integrity.Rule; - -import com.android.server.integrity.parser.BinaryFileOperations; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -import java.io.IOException; - -@RunWith(JUnit4.class) -public class BitTrackedInputStreamTest { - private static final String COMPOUND_FORMULA_START_BITS = - getBits(COMPOUND_FORMULA_START, SEPARATOR_BITS); - private static final String COMPOUND_FORMULA_END_BITS = - getBits(COMPOUND_FORMULA_END, SEPARATOR_BITS); - private static final String ATOMIC_FORMULA_START_BITS = - getBits(ATOMIC_FORMULA_START, SEPARATOR_BITS); - private static final String NOT = getBits(CompoundFormula.NOT, CONNECTOR_BITS); - private static final String PACKAGE_NAME = getBits(AtomicFormula.PACKAGE_NAME, KEY_BITS); - private static final String EQ = getBits(AtomicFormula.EQ, OPERATOR_BITS); - private static final String DENY = getBits(Rule.DENY, EFFECT_BITS); - - private static final String IS_NOT_HASHED = "0"; - private static final String START_BIT = "1"; - private static final String END_BIT = "1"; - - @Test - public void testBitOperationsCountBitsCorrectly() throws IOException { - String packageName = "com.test.app"; - byte[] testInput = - getBytes( - START_BIT - + COMPOUND_FORMULA_START_BITS - + NOT - + ATOMIC_FORMULA_START_BITS - + PACKAGE_NAME - + EQ - + IS_NOT_HASHED - + getBits(packageName.length(), VALUE_SIZE_BITS) - + getValueBits(packageName) - + COMPOUND_FORMULA_END_BITS - + DENY - + END_BIT); - - BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(testInput); - - // Right after construction, the read bits count should be 0. - assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(0); - - // Get next 10 bits should result with 10 bits read. - bitTrackedInputStream.getNext(10); - assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(10); - - // When we move the cursor 8 bytes, we should point to 64 bits. - bitTrackedInputStream.setCursorToByteLocation(8); - assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(64); - - // Read until the end and the total size of the input stream should be available. - while (bitTrackedInputStream.hasNext()) { - bitTrackedInputStream.getNext(1); - } - assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(128); - } - - @Test - public void testBitInputStreamOperationsStillWork() throws IOException { - String packageName = "com.test.app"; - byte[] testInput = - getBytes( - IS_NOT_HASHED - + getBits(packageName.length(), VALUE_SIZE_BITS) - + getValueBits(packageName)); - - BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(testInput); - assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(0); - - // Read until the string parameter. - String stringValue = BinaryFileOperations.getStringValue(bitTrackedInputStream); - - // Verify that the read bytes are counted. - assertThat(stringValue).isEqualTo(packageName); - assertThat(bitTrackedInputStream.getReadBitsCount()).isGreaterThan(0); - } - - @Test - public void testBitTrackedInputStream_moveCursorForwardFailsIfAlreadyRead() throws IOException { - String packageName = "com.test.app"; - byte[] testInput = - getBytes( - IS_NOT_HASHED - + getBits(packageName.length(), VALUE_SIZE_BITS) - + getValueBits(packageName)); - - BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(testInput); - - // Read more than two bytes. - bitTrackedInputStream.getNext(20); - - // Ask to move the cursor to the second byte. - assertThrows( - IllegalStateException.class, - () -> bitTrackedInputStream.setCursorToByteLocation(2)); - } -} diff --git a/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java index c7cc343dd77e..57274bf13499 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java @@ -53,17 +53,17 @@ public class ByteTrackedOutputStreamTest { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ByteTrackedOutputStream byteTrackedOutputStream = new ByteTrackedOutputStream(outputStream); - BitOutputStream bitOutputStream = new BitOutputStream(); + BitOutputStream bitOutputStream = new BitOutputStream(byteTrackedOutputStream); bitOutputStream.setNext(/* numOfBits= */5, /* value= */1); - byteTrackedOutputStream.write(bitOutputStream.toByteArray()); + bitOutputStream.flush(); // Even though we wrote 5 bits, this will complete to 1 byte. assertThat(byteTrackedOutputStream.getWrittenBytesCount()).isEqualTo(1); // Add a bit less than 2 bytes (10 bits). - bitOutputStream.clear(); bitOutputStream.setNext(/* numOfBits= */10, /* value= */1); - byteTrackedOutputStream.write(bitOutputStream.toByteArray()); + bitOutputStream.flush(); + assertThat(byteTrackedOutputStream.getWrittenBytesCount()).isEqualTo(3); assertThat(outputStream.toByteArray().length).isEqualTo(3); } diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/BinaryFileOperationsTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/BinaryFileOperationsTest.java index 94f68a59072e..cfa1de371e8c 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/parser/BinaryFileOperationsTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/parser/BinaryFileOperationsTest.java @@ -33,8 +33,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.io.ByteArrayInputStream; import java.io.IOException; -import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; @RunWith(JUnit4.class) @@ -52,9 +52,7 @@ public class BinaryFileOperationsTest { IS_NOT_HASHED + getBits(PACKAGE_NAME.length(), VALUE_SIZE_BITS) + getValueBits(PACKAGE_NAME)); - ByteBuffer rule = ByteBuffer.allocate(stringBytes.length); - rule.put(stringBytes); - BitInputStream inputStream = new BitInputStream(rule.array()); + BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(stringBytes)); String resultString = getStringValue(inputStream); @@ -68,9 +66,7 @@ public class BinaryFileOperationsTest { IS_HASHED + getBits(APP_CERTIFICATE.length(), VALUE_SIZE_BITS) + getValueBits(APP_CERTIFICATE)); - ByteBuffer rule = ByteBuffer.allocate(ruleBytes.length); - rule.put(ruleBytes); - BitInputStream inputStream = new BitInputStream(rule.array()); + BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(ruleBytes)); String resultString = getStringValue(inputStream); @@ -82,9 +78,7 @@ public class BinaryFileOperationsTest { @Test public void testGetStringValue_withSizeAndHashingInfo() throws IOException { byte[] ruleBytes = getBytes(getValueBits(PACKAGE_NAME)); - ByteBuffer rule = ByteBuffer.allocate(ruleBytes.length); - rule.put(ruleBytes); - BitInputStream inputStream = new BitInputStream(rule.array()); + BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(ruleBytes)); String resultString = getStringValue(inputStream, PACKAGE_NAME.length(), /* isHashedValue= */false); @@ -96,9 +90,7 @@ public class BinaryFileOperationsTest { public void testGetIntValue() throws IOException { int randomValue = 15; byte[] ruleBytes = getBytes(getBits(randomValue, /* numOfBits= */ 32)); - ByteBuffer rule = ByteBuffer.allocate(ruleBytes.length); - rule.put(ruleBytes); - BitInputStream inputStream = new BitInputStream(rule.array()); + BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(ruleBytes)); assertThat(getIntValue(inputStream)).isEqualTo(randomValue); } @@ -107,9 +99,7 @@ public class BinaryFileOperationsTest { public void testGetBooleanValue_true() throws IOException { String booleanValue = "1"; byte[] ruleBytes = getBytes(booleanValue); - ByteBuffer rule = ByteBuffer.allocate(ruleBytes.length); - rule.put(ruleBytes); - BitInputStream inputStream = new BitInputStream(rule.array()); + BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(ruleBytes)); assertThat(getBooleanValue(inputStream)).isEqualTo(true); } @@ -118,9 +108,7 @@ public class BinaryFileOperationsTest { public void testGetBooleanValue_false() throws IOException { String booleanValue = "0"; byte[] ruleBytes = getBytes(booleanValue); - ByteBuffer rule = ByteBuffer.allocate(ruleBytes.length); - rule.put(ruleBytes); - BitInputStream inputStream = new BitInputStream(rule.array()); + BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(ruleBytes)); assertThat(getBooleanValue(inputStream)).isEqualTo(false); } diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java index 881b3d6bba52..e0b2e2257ee4 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java @@ -44,8 +44,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import java.io.ByteArrayInputStream; -import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Arrays; @@ -121,7 +119,6 @@ public class RuleBinaryParserTest { rule.put(DEFAULT_FORMAT_VERSION_BYTES); rule.put(ruleBytes); RuleParser binaryParser = new RuleBinaryParser(); - InputStream inputStream = new ByteArrayInputStream(rule.array()); Rule expectedRule = new Rule( new CompoundFormula( @@ -133,7 +130,8 @@ public class RuleBinaryParserTest { /* isHashedValue= */ false))), Rule.DENY); - List<Rule> rules = binaryParser.parse(inputStream, NO_INDEXING); + List<Rule> rules = + binaryParser.parse(RandomAccessObject.ofBytes(rule.array()), NO_INDEXING); assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); } @@ -323,6 +321,7 @@ public class RuleBinaryParserTest { ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); rule.put(DEFAULT_FORMAT_VERSION_BYTES); rule.put(ruleBytes); + RuleParser binaryParser = new RuleBinaryParser(); Rule expectedRule = new Rule( @@ -412,7 +411,7 @@ public class RuleBinaryParserTest { assertExpectException( RuleParseException.class, - /* expectedExceptionMessageRegex */ "A rule must end with a '1' bit.", + /* expectedExceptionMessageRegex= */ "A rule must end with a '1' bit.", () -> binaryParser.parse(rule.array())); } @@ -439,7 +438,7 @@ public class RuleBinaryParserTest { assertExpectException( RuleParseException.class, - /* expectedExceptionMessageRegex */ "Invalid byte index", + /* expectedExceptionMessageRegex */ "", () -> binaryParser.parse(rule.array())); } diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java index 6944aee7fcb9..0f0dee924e29 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java @@ -28,8 +28,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import java.io.ByteArrayInputStream; -import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collections; @@ -61,7 +59,6 @@ public class RuleXmlParserTest { + "</R>" + "</RL>"; RuleParser xmlParser = new RuleXmlParser(); - InputStream inputStream = new ByteArrayInputStream(ruleXmlCompoundFormula.getBytes()); Rule expectedRule = new Rule( new CompoundFormula( @@ -73,7 +70,7 @@ public class RuleXmlParserTest { /* isHashedValue= */ false))), Rule.DENY); - List<Rule> rules = xmlParser.parse(inputStream, Collections.emptyList()); + List<Rule> rules = xmlParser.parse(ruleXmlCompoundFormula.getBytes()); assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); } @@ -617,13 +614,12 @@ public class RuleXmlParserTest { /* tag= */ "AF", atomicFormulaAttrs, /* closed= */ true) + "</OF>" + "</R>"; - InputStream inputStream = new ByteArrayInputStream(ruleXmlWithNoRuleList.getBytes()); RuleParser xmlParser = new RuleXmlParser(); assertExpectException( RuleParseException.class, /* expectedExceptionMessageRegex */ "Rules must start with RuleList <RL> tag", - () -> xmlParser.parse(inputStream, Collections.emptyList())); + () -> xmlParser.parse(ruleXmlWithNoRuleList.getBytes())); } private String generateTagWithAttribute( diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java index eb6698b0d479..8ee5f5fb9de8 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java @@ -135,15 +135,16 @@ public class RuleBinarySerializerTest { .isEqualTo(expectedRuleOutputStream.toByteArray()); ByteArrayOutputStream expectedIndexingOutputStream = new ByteArrayOutputStream(); + String serializedIndexingBytes = + SERIALIZED_START_INDEXING_KEY + + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32) + + SERIALIZED_END_INDEXING_KEY + + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32); byte[] expectedIndexingBytes = getBytes( - SERIALIZED_START_INDEXING_KEY - + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32) - + SERIALIZED_END_INDEXING_KEY - + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ - 32)); - expectedIndexingOutputStream.write(expectedIndexingBytes); - expectedIndexingOutputStream.write(expectedIndexingBytes); + serializedIndexingBytes + + serializedIndexingBytes + + serializedIndexingBytes); expectedIndexingOutputStream.write(expectedIndexingBytes); assertThat(indexingOutputStream.toByteArray()) .isEqualTo(expectedIndexingOutputStream.toByteArray()); @@ -197,15 +198,19 @@ public class RuleBinarySerializerTest { + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32) + SERIALIZED_END_INDEXING_KEY + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32); - expectedIndexingOutputStream.write(getBytes(expectedIndexingBitsForIndexed)); - expectedIndexingOutputStream.write(getBytes(expectedIndexingBitsForIndexed)); String expectedIndexingBitsForUnindexed = SERIALIZED_START_INDEXING_KEY + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32) + SERIALIZED_END_INDEXING_KEY - + getBits(DEFAULT_FORMAT_VERSION_BYTES.length + getBytes( - expectedBits).length, /* numOfBits= */ 32); - expectedIndexingOutputStream.write(getBytes(expectedIndexingBitsForUnindexed)); + + getBits( + DEFAULT_FORMAT_VERSION_BYTES.length + getBytes(expectedBits).length, + /* numOfBits= */ 32); + expectedIndexingOutputStream.write( + getBytes( + expectedIndexingBitsForIndexed + + expectedIndexingBitsForIndexed + + expectedIndexingBitsForUnindexed)); + assertThat(indexingOutputStream.toByteArray()) .isEqualTo(expectedIndexingOutputStream.toByteArray()); } @@ -513,16 +518,19 @@ public class RuleBinarySerializerTest { // and 225 non-indexed rules.. List<Rule> ruleList = new ArrayList(); for (int count = 0; count < ruleCount; count++) { - ruleList.add(getRuleWithPackageNameAndSampleInstallerName( - String.format("%s%04d", packagePrefix, count))); + ruleList.add( + getRuleWithPackageNameAndSampleInstallerName( + String.format("%s%04d", packagePrefix, count))); } for (int count = 0; count < ruleCount; count++) { - ruleList.add(getRuleWithAppCertificateAndSampleInstallerName( - String.format("%s%04d", appCertificatePrefix, count))); + ruleList.add( + getRuleWithAppCertificateAndSampleInstallerName( + String.format("%s%04d", appCertificatePrefix, count))); } for (int count = 0; count < ruleCount; count++) { - ruleList.add(getNonIndexedRuleWithInstallerName( - String.format("%s%04d", installerNamePrefix, count))); + ruleList.add( + getNonIndexedRuleWithInstallerName( + String.format("%s%04d", installerNamePrefix, count))); } // Serialize the rules. @@ -543,8 +551,7 @@ public class RuleBinarySerializerTest { int totalBytesWritten = DEFAULT_FORMAT_VERSION_BYTES.length; String expectedIndexingBytesForPackageNameIndexed = - SERIALIZED_START_INDEXING_KEY - + getBits(totalBytesWritten, /* numOfBits= */ 32); + SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32); for (int count = 0; count < ruleCount; count++) { String packageName = String.format("%s%04d", packagePrefix, count); if (count > 0 && count % INDEXING_BLOCK_SIZE == 0) { @@ -556,19 +563,17 @@ public class RuleBinarySerializerTest { } byte[] bytesForPackage = - getBytes(getSerializedCompoundRuleWithPackageNameAndSampleInstallerName( - packageName)); + getBytes( + getSerializedCompoundRuleWithPackageNameAndSampleInstallerName( + packageName)); expectedOrderedRuleOutputStream.write(bytesForPackage); totalBytesWritten += bytesForPackage.length; } expectedIndexingBytesForPackageNameIndexed += - SERIALIZED_END_INDEXING_KEY - + getBits(totalBytesWritten, /* numOfBits= */ 32); - expectedIndexingOutputStream.write(getBytes(expectedIndexingBytesForPackageNameIndexed)); + SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32); String expectedIndexingBytesForAppCertificateIndexed = - SERIALIZED_START_INDEXING_KEY - + getBits(totalBytesWritten, /* numOfBits= */ 32); + SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32); for (int count = 0; count < ruleCount; count++) { String appCertificate = String.format("%s%04d", appCertificatePrefix, count); if (count > 0 && count % INDEXING_BLOCK_SIZE == 0) { @@ -580,31 +585,32 @@ public class RuleBinarySerializerTest { } byte[] bytesForPackage = - getBytes(getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName( - appCertificate)); + getBytes( + getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName( + appCertificate)); expectedOrderedRuleOutputStream.write(bytesForPackage); totalBytesWritten += bytesForPackage.length; } expectedIndexingBytesForAppCertificateIndexed += - SERIALIZED_END_INDEXING_KEY - + getBits(totalBytesWritten, /* numOfBits= */ 32); - expectedIndexingOutputStream.write(getBytes(expectedIndexingBytesForAppCertificateIndexed)); + SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32); String expectedIndexingBytesForUnindexed = - SERIALIZED_START_INDEXING_KEY - + getBits(totalBytesWritten, /* numOfBits= */ 32); + SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32); for (int count = 0; count < ruleCount; count++) { byte[] bytesForPackage = - getBytes(getSerializedCompoundRuleWithInstallerNameAndInstallerCert( - String.format("%s%04d", installerNamePrefix, count))); + getBytes( + getSerializedCompoundRuleWithInstallerNameAndInstallerCert( + String.format("%s%04d", installerNamePrefix, count))); expectedOrderedRuleOutputStream.write(bytesForPackage); totalBytesWritten += bytesForPackage.length; } expectedIndexingBytesForUnindexed += - SERIALIZED_END_INDEXING_KEY - + getBits(totalBytesWritten, /* numOfBits= */ 32); - expectedIndexingOutputStream.write(getBytes(expectedIndexingBytesForUnindexed)); - + SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32); + expectedIndexingOutputStream.write( + getBytes( + expectedIndexingBytesForPackageNameIndexed + + expectedIndexingBytesForAppCertificateIndexed + + expectedIndexingBytesForUnindexed)); assertThat(ruleOutputStream.toByteArray()) .isEqualTo(expectedOrderedRuleOutputStream.toByteArray()); diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java index 55fada44b99e..1674422f3af9 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java @@ -38,10 +38,8 @@ import org.junit.runners.JUnit4; import java.util.ArrayList; import java.util.Arrays; -import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.TreeMap; /** Unit tests for {@link RuleIndexingDetailsIdentifier}. */ @RunWith(JUnit4.class) @@ -140,7 +138,7 @@ public class RuleIndexingDetailsIdentifierTest { List<Rule> ruleList = new ArrayList(); ruleList.add(RULE_WITH_PACKAGE_NAME); - Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); + Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); // Verify the resulting map content. assertThat(result.keySet()) @@ -157,7 +155,7 @@ public class RuleIndexingDetailsIdentifierTest { List<Rule> ruleList = new ArrayList(); ruleList.add(RULE_WITH_APP_CERTIFICATE); - Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); + Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); assertThat(result.keySet()) .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED); @@ -174,7 +172,7 @@ public class RuleIndexingDetailsIdentifierTest { List<Rule> ruleList = new ArrayList(); ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS); - Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); + Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); assertThat(result.keySet()) .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED); @@ -189,7 +187,7 @@ public class RuleIndexingDetailsIdentifierTest { List<Rule> ruleList = new ArrayList(); ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS); - Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); + Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); assertThat(result.keySet()) .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED); @@ -215,7 +213,7 @@ public class RuleIndexingDetailsIdentifierTest { List<Rule> ruleList = new ArrayList(); ruleList.add(negatedRule); - Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); + Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); assertThat(result.keySet()) .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED); @@ -225,7 +223,7 @@ public class RuleIndexingDetailsIdentifierTest { } @Test - public void getIndexType_allRulesTogetherInValidOrder() { + public void getIndexType_allRulesTogetherSplitCorrectly() { Rule packageNameRuleA = getRuleWithPackageName("aaa"); Rule packageNameRuleB = getRuleWithPackageName("bbb"); Rule packageNameRuleC = getRuleWithPackageName("ccc"); @@ -243,38 +241,20 @@ public class RuleIndexingDetailsIdentifierTest { ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS); ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS); - Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); + Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); assertThat(result.keySet()) .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED); // We check asserts this way to ensure ordering based on package name. assertThat(result.get(PACKAGE_NAME_INDEXED).keySet()).containsExactly("aaa", "bbb", "ccc"); - Iterator<String> keySetIterator = result.get(PACKAGE_NAME_INDEXED).keySet().iterator(); - assertThat(keySetIterator.next()).isEqualTo("aaa"); - assertThat(keySetIterator.next()).isEqualTo("bbb"); - assertThat(keySetIterator.next()).isEqualTo("ccc"); - assertThat(result.get(PACKAGE_NAME_INDEXED).get("aaa")).containsExactly(packageNameRuleA); - assertThat(result.get(PACKAGE_NAME_INDEXED).get("bbb")).containsExactly(packageNameRuleB); - assertThat(result.get(PACKAGE_NAME_INDEXED).get("ccc")).containsExactly(packageNameRuleC); // We check asserts this way to ensure ordering based on app certificate. assertThat(result.get(APP_CERTIFICATE_INDEXED).keySet()).containsExactly("cert1", "cert2", "cert3"); - keySetIterator = result.get(APP_CERTIFICATE_INDEXED).keySet().iterator(); - assertThat(keySetIterator.next()).isEqualTo("cert1"); - assertThat(keySetIterator.next()).isEqualTo("cert2"); - assertThat(keySetIterator.next()).isEqualTo("cert3"); - assertThat(result.get(APP_CERTIFICATE_INDEXED).get("cert1")).containsExactly( - certificateRule1); - assertThat(result.get(APP_CERTIFICATE_INDEXED).get("cert2")).containsExactly( - certificateRule2); - assertThat(result.get(APP_CERTIFICATE_INDEXED).get("cert3")).containsExactly( - certificateRule3); assertThat(result.get(NOT_INDEXED).get("N/A")) - .containsExactly( - RULE_WITH_INSTALLER_RESTRICTIONS, + .containsExactly(RULE_WITH_INSTALLER_RESTRICTIONS, RULE_WITH_NONSTRING_RESTRICTIONS); } diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java index e1c489e65984..355cadaa1de8 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -112,7 +112,7 @@ import android.net.NetworkState; import android.net.NetworkStats; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; -import android.net.StringNetworkSpecifier; +import android.net.TelephonyNetworkSpecifier; import android.os.Binder; import android.os.Handler; import android.os.INetworkManagementService; @@ -1804,9 +1804,11 @@ public class NetworkPolicyManagerServiceTest { .getService(NetworkPolicyManagerInternal.class); npmi.onStatsProviderLimitReached("TEST"); - // Verifies that the limit reached leads to a force update. + // Verifies that the limit reached leads to a force update and new limit should be set. postMsgAndWaitForCompletion(); verify(mStatsService).forceUpdate(); + postMsgAndWaitForCompletion(); + verify(mStatsService).setStatsProviderLimit(TEST_IFACE, 10000L - 4999L - 1999L); } /** @@ -1911,7 +1913,8 @@ public class NetworkPolicyManagerServiceTest { if (!roaming) { nc.addCapability(NET_CAPABILITY_NOT_ROAMING); } - nc.setNetworkSpecifier(new StringNetworkSpecifier(String.valueOf(subId))); + nc.setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder() + .setSubscriptionId(subId).build()); return nc; } 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 82bbdcba5bc1..cb9d816509b9 100644 --- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java @@ -28,10 +28,12 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.parsing.AndroidPackage; +import android.content.pm.parsing.ComponentParseUtils; import android.content.pm.parsing.ComponentParseUtils.ParsedActivity; import android.content.pm.parsing.ComponentParseUtils.ParsedActivityIntentInfo; import android.content.pm.parsing.PackageImpl; import android.content.pm.parsing.ParsingPackage; +import android.net.Uri; import android.os.Build; import android.os.Process; import android.util.ArrayMap; @@ -49,6 +51,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Set; @@ -116,6 +119,15 @@ public class AppsFilterTest { .addActivity(activity); } + private static ParsingPackage pkgWithProvider(String packageName, String authority) { + ComponentParseUtils.ParsedProvider provider = new ComponentParseUtils.ParsedProvider(); + provider.setPackageName(packageName); + provider.setExported(true); + provider.setAuthority(authority); + return pkg(packageName) + .addProvider(provider); + } + @Before public void setup() throws Exception { mExisting = new ArrayMap<>(); @@ -149,6 +161,55 @@ public class AppsFilterTest { } @Test + public void testQueriesProvider_FilterMatches() { + final AppsFilter appsFilter = + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + appsFilter.onSystemReady(); + + PackageSetting target = simulateAddPackage(appsFilter, + pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_UID); + PackageSetting calling = simulateAddPackage(appsFilter, + pkg("com.some.other.package", + new Intent().setData(Uri.parse("content://com.some.authority"))), + DUMMY_CALLING_UID); + + assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); + } + + @Test + public void testQueriesDifferentProvider_Filters() { + final AppsFilter appsFilter = + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + appsFilter.onSystemReady(); + + PackageSetting target = simulateAddPackage(appsFilter, + pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_UID); + PackageSetting calling = simulateAddPackage(appsFilter, + pkg("com.some.other.package", + new Intent().setData(Uri.parse("content://com.some.other.authority"))), + DUMMY_CALLING_UID); + + assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); + } + + @Test + public void testQueriesProviderWithSemiColon_FilterMatches() { + final AppsFilter appsFilter = + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + appsFilter.onSystemReady(); + + PackageSetting target = simulateAddPackage(appsFilter, + pkgWithProvider("com.some.package", "com.some.authority;com.some.other.authority"), + DUMMY_TARGET_UID); + PackageSetting calling = simulateAddPackage(appsFilter, + pkg("com.some.other.package", + new Intent().setData(Uri.parse("content://com.some.authority"))), + DUMMY_CALLING_UID); + + assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); + } + + @Test public void testQueriesAction_NoMatchingAction_Filters() { final AppsFilter appsFilter = new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java index f08044c0b5b5..f5e5e2a13bcf 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java @@ -160,6 +160,31 @@ public class DexManagerTests { } @Test + public void testNotifyPrimaryAndSecondary() { + List<String> dexFiles = mFooUser0.getBaseAndSplitDexPaths(); + List<String> secondaries = mFooUser0.getSecondaryDexPaths(); + int baseAndSplitCount = dexFiles.size(); + dexFiles.addAll(secondaries); + + notifyDexLoad(mFooUser0, dexFiles, mUser0); + + PackageUseInfo pui = getPackageUseInfo(mFooUser0); + assertIsUsedByOtherApps(mFooUser0, pui, false); + assertEquals(secondaries.size(), pui.getDexUseInfoMap().size()); + + String[] allExpectedContexts = DexoptUtils.processContextForDexLoad( + Arrays.asList(mFooUser0.mClassLoader), + Arrays.asList(String.join(File.pathSeparator, dexFiles))); + String[] secondaryExpectedContexts = Arrays.copyOfRange(allExpectedContexts, + baseAndSplitCount, dexFiles.size()); + + assertSecondaryUse(mFooUser0, pui, secondaries, /*isUsedByOtherApps*/false, mUser0, + secondaryExpectedContexts); + + assertHasDclInfo(mFooUser0, mFooUser0, secondaries); + } + + @Test public void testNotifySecondaryForeign() { // Foo loads bar secondary files. List<String> barSecondaries = mBarUser0.getSecondaryDexPaths(); diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java index cc170af2c57b..c56034adb73d 100644 --- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java +++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java @@ -335,7 +335,9 @@ public class SoundTriggerMiddlewareImplTest { }).when(driver).getProperties_2_3(any()); } - mService = new SoundTriggerMiddlewareImpl(mHalDriver, mAudioSessionProvider); + mService = new SoundTriggerMiddlewareImpl(() -> { + return mHalDriver; + }, mAudioSessionProvider); } private Pair<Integer, SoundTriggerHwCallback> loadGenericModel_2_0(ISoundTriggerModule module, @@ -798,12 +800,12 @@ public class SoundTriggerMiddlewareImplTest { @Override public boolean linkToDeath(DeathRecipient recipient, long cookie) { - throw new UnsupportedOperationException(); + return true; } @Override public boolean unlinkToDeath(DeathRecipient recipient) { - throw new UnsupportedOperationException(); + return true; } }; diff --git a/services/tests/servicestests/src/com/android/server/stats/IonMemoryUtilTest.java b/services/tests/servicestests/src/com/android/server/stats/pull/IonMemoryUtilTest.java index 8cbf8e56edeb..d4d4b4d6a8c9 100644 --- a/services/tests/servicestests/src/com/android/server/stats/IonMemoryUtilTest.java +++ b/services/tests/servicestests/src/com/android/server/stats/pull/IonMemoryUtilTest.java @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.server.stats; +package com.android.server.stats.pull; -import static com.android.server.stats.IonMemoryUtil.parseIonHeapSizeFromDebugfs; -import static com.android.server.stats.IonMemoryUtil.parseProcessIonHeapSizesFromDebugfs; +import static com.android.server.stats.pull.IonMemoryUtil.parseIonHeapSizeFromDebugfs; +import static com.android.server.stats.pull.IonMemoryUtil.parseProcessIonHeapSizesFromDebugfs; import static com.google.common.truth.Truth.assertThat; import androidx.test.filters.SmallTest; -import com.android.server.stats.IonMemoryUtil.IonAllocations; +import com.android.server.stats.pull.IonMemoryUtil.IonAllocations; import org.junit.Test; diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java index 8a3183f7abbd..d940a6a320f2 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java @@ -651,7 +651,6 @@ public class TimeDetectorStrategyImplTest { @Override public long systemClockMillis() { - assertWakeLockAcquired(); return mSystemClockMillis; } diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index 1dd7e64690c7..6aca58f400b3 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -23,7 +23,8 @@ import static android.app.usage.UsageEvents.Event.SLICE_PINNED_PRIV; import static android.app.usage.UsageEvents.Event.SYSTEM_INTERACTION; import static android.app.usage.UsageEvents.Event.USER_INTERACTION; import static android.app.usage.UsageStatsManager.REASON_MAIN_DEFAULT; -import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED; +import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_SYSTEM; +import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER; import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED; import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT; import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE; @@ -501,12 +502,18 @@ public class AppStandbyControllerTests { // Can force to NEVER mInjector.mElapsedRealtime = HOUR_MS; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER, - REASON_MAIN_FORCED); + REASON_MAIN_FORCED_BY_USER); assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_1)); - // Prediction can't override FORCED reason + // Prediction can't override FORCED reasons + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, + REASON_MAIN_FORCED_BY_SYSTEM); + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, + REASON_MAIN_PREDICTED); + assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, - REASON_MAIN_FORCED); + REASON_MAIN_FORCED_BY_USER); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, REASON_MAIN_PREDICTED); assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController, PACKAGE_1)); @@ -631,7 +638,7 @@ public class AppStandbyControllerTests { mInjector.mElapsedRealtime = 1 * RARE_THRESHOLD + 100; // Make sure app is in NEVER bucket mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER, - REASON_MAIN_FORCED); + REASON_MAIN_FORCED_BY_USER); mController.checkIdleStates(USER_ID); assertBucket(STANDBY_BUCKET_NEVER); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java index bd7d9ecf84d1..f5af3ec76788 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java @@ -64,7 +64,8 @@ public class NotificationChannelExtractorTest extends UiServiceTestCase { NotificationChannel updatedChannel = new NotificationChannel("a", "", IMPORTANCE_HIGH); - when(mConfig.getNotificationChannel(any(), anyInt(), eq("a"), eq(null), eq(false))) + when(mConfig.getConversationNotificationChannel( + any(), anyInt(), eq("a"), eq(null), eq(true), eq(false))) .thenReturn(updatedChannel); assertNull(extractor.process(r)); 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 62f52306ea49..2ac464251c73 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -471,16 +471,17 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mTestableLooper.processAllMessages(); } - private void setUpPrefsForBubbles(boolean globalEnabled, boolean pkgEnabled, - boolean channelEnabled) { - mService.setPreferencesHelper(mPreferencesHelper); - when(mPreferencesHelper.bubblesEnabled()).thenReturn(globalEnabled); - when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(pkgEnabled); - when(mPreferencesHelper.getNotificationChannel( - anyString(), anyInt(), anyString(), eq(null), anyBoolean())).thenReturn( - mTestNotificationChannel); - when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn( - mTestNotificationChannel.getImportance()); + private void setUpPrefsForBubbles(String pkg, int uid, boolean globalEnabled, + boolean pkgEnabled, boolean channelEnabled) { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.NOTIFICATION_BUBBLES, globalEnabled ? 1 : 0); + mService.mPreferencesHelper.updateBubblesEnabled(); + assertEquals(globalEnabled, mService.mPreferencesHelper.bubblesEnabled()); + try { + mBinderService.setBubblesAllowed(pkg, uid, pkgEnabled); + } catch (RemoteException e) { + e.printStackTrace(); + } mTestNotificationChannel.setAllowBubbles(channelEnabled); } @@ -1763,8 +1764,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo"); mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_onTv", 0, generateNotificationRecord(null, tv).getNotification(), 0); - verify(mPreferencesHelper, times(1)).getNotificationChannel( - anyString(), anyInt(), eq("foo"), eq(null), anyBoolean()); + verify(mPreferencesHelper, times(1)).getConversationNotificationChannel( + anyString(), anyInt(), eq("foo"), eq(null), anyBoolean(), anyBoolean()); } @Test @@ -1778,9 +1779,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo"); mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_notOnTv", 0, generateNotificationRecord(null, tv).getNotification(), 0); - verify(mPreferencesHelper, times(1)).getNotificationChannel( + verify(mPreferencesHelper, times(1)).getConversationNotificationChannel( anyString(), anyInt(), eq(mTestNotificationChannel.getId()), eq(null), - anyBoolean()); + anyBoolean(), anyBoolean()); } @Test @@ -4759,7 +4760,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testFlagBubble() throws RemoteException { // Bubbles are allowed! - setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */); + setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */); NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "testFlagBubble"); @@ -4778,7 +4779,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testFlagBubble_noFlag_appNotAllowed() throws RemoteException { // Bubbles are allowed! - setUpPrefsForBubbles(true /* global */, false /* app */, true /* channel */); + setUpPrefsForBubbles(PKG, mUid, true /* global */, false /* app */, true /* channel */); NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "testFlagBubble_noFlag_appNotAllowed"); @@ -4797,7 +4798,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testFlagBubbleNotifs_noFlag_whenAppForeground() throws RemoteException { // Bubbles are allowed! - setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */); + setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */); // Notif with bubble metadata but not our other misc requirements Notification.Builder nb = new Notification.Builder(mContext, @@ -4825,7 +4826,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testFlagBubbleNotifs_flag_messaging() throws RemoteException { // Bubbles are allowed! - setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */); + setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */); NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "testFlagBubbleNotifs_flag_messaging"); @@ -4842,7 +4843,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testFlagBubbleNotifs_flag_phonecall() throws RemoteException { // Bubbles are allowed! - setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */); + setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */); // Give it bubble metadata Notification.BubbleMetadata data = getBubbleMetadataBuilder().build(); @@ -4878,7 +4879,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testFlagBubbleNotifs_noFlag_phonecall_noForegroundService() throws RemoteException { // Bubbles are allowed! - setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */); + setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */); // Give it bubble metadata Notification.BubbleMetadata data = getBubbleMetadataBuilder().build(); @@ -4911,7 +4912,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testFlagBubbleNotifs_noFlag_phonecall_noPerson() throws RemoteException { // Bubbles are allowed! - setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */); + setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */); // Give it bubble metadata Notification.BubbleMetadata data = getBubbleMetadataBuilder().build(); @@ -4942,7 +4943,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testFlagBubbleNotifs_noFlag_phonecall_noCategory() throws RemoteException { // Bubbles are allowed! - setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */); + setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */); // Give it bubble metadata Notification.BubbleMetadata data = getBubbleMetadataBuilder().build(); @@ -4977,7 +4978,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testFlagBubbleNotifs_noFlag_messaging_appNotAllowed() throws RemoteException { // Bubbles are NOT allowed! - setUpPrefsForBubbles(false /* global */, true /* app */, true /* channel */); + setUpPrefsForBubbles(PKG, mUid, true /* global */, false /* app */, true /* channel */); NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "testFlagBubbleNotifs_noFlag_messaging_appNotAllowed"); @@ -4995,7 +4996,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testFlagBubbleNotifs_noFlag_notBubble() throws RemoteException { // Bubbles are allowed! - setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */); + setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */); // Messaging notif WITHOUT bubble metadata Notification.Builder nb = getMessageStyleNotifBuilder(false /* addBubbleMetadata */, @@ -5019,7 +5020,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testFlagBubbleNotifs_noFlag_messaging_channelNotAllowed() throws RemoteException { // Bubbles are allowed except on this channel - setUpPrefsForBubbles(true /* global */, true /* app */, false /* channel */); + setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, false /* channel */); NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "testFlagBubbleNotifs_noFlag_messaging_channelNotAllowed"); @@ -5037,7 +5038,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testFlagBubbleNotifs_noFlag_phonecall_notAllowed() throws RemoteException { // Bubbles are not allowed! - setUpPrefsForBubbles(false /* global */, true /* app */, true /* channel */); + setUpPrefsForBubbles(PKG, mUid, false /* global */, true /* app */, true /* channel */); // Give it bubble metadata Notification.BubbleMetadata data = getBubbleMetadataBuilder().build(); @@ -5072,7 +5073,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testFlagBubbleNotifs_noFlag_phonecall_channelNotAllowed() throws RemoteException { // Bubbles are allowed, but not on channel. - setUpPrefsForBubbles(true /* global */, true /* app */, false /* channel */); + setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, false /* channel */); // Give it bubble metadata Notification.BubbleMetadata data = getBubbleMetadataBuilder().build(); @@ -5280,7 +5281,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testNotificationBubbleChanged_false() throws Exception { // Bubbles are allowed! - setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */); + setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */); // Notif with bubble metadata NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, @@ -5311,7 +5312,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testNotificationBubbleChanged_true() throws Exception { // Bubbles are allowed! - setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */); + setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */); // Notif that is not a bubble NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, @@ -5348,7 +5349,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testNotificationBubbleChanged_true_notAllowed() throws Exception { // Bubbles are allowed! - setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */); + setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */); // Notif that is not a bubble NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel); @@ -5547,7 +5548,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testNotificationBubbles_disabled_lowRamDevice() throws Exception { // Bubbles are allowed! - setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */); + setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */); // And we are low ram when(mActivityManager.isLowRamDevice()).thenReturn(true); @@ -5631,7 +5632,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testNotificationBubbles_flagAutoExpandForeground_fails_notForeground() throws Exception { // Bubbles are allowed! - setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */); + setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */); NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "testNotificationBubbles_flagAutoExpandForeground_fails_notForeground"); @@ -5661,7 +5662,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testNotificationBubbles_flagAutoExpandForeground_succeeds_foreground() throws RemoteException { // Bubbles are allowed! - setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */); + setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */); NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "testNotificationBubbles_flagAutoExpandForeground_succeeds_foreground"); @@ -5691,7 +5692,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testNotificationBubbles_bubbleChildrenStay_whenGroupSummaryDismissed() throws Exception { // Bubbles are allowed! - setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */); + setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */); NotificationRecord nrSummary = addGroupWithBubblesAndValidateAdded( true /* summaryAutoCancel */); @@ -5714,7 +5715,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testNotificationBubbles_bubbleChildrenStay_whenGroupSummaryClicked() throws Exception { // Bubbles are allowed! - setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */); + setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */); NotificationRecord nrSummary = addGroupWithBubblesAndValidateAdded( true /* summaryAutoCancel */); @@ -5836,7 +5837,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.createConversationNotificationChannelForPackage(PKG, mUid, orig, "friend"); NotificationChannel friendChannel = mBinderService.getConversationNotificationChannel( - PKG, 0, PKG, original.getId(), "friend"); + PKG, 0, PKG, original.getId(), false, "friend"); assertEquals(original.getName(), friendChannel.getName()); assertEquals(original.getId(), friendChannel.getParentChannelId()); @@ -5874,9 +5875,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { conversationId); NotificationChannel messagesChild = mBinderService.getConversationNotificationChannel( - PKG, 0, PKG, messagesParent.getId(), conversationId); + PKG, 0, PKG, messagesParent.getId(), false, conversationId); NotificationChannel callsChild = mBinderService.getConversationNotificationChannel( - PKG, 0, PKG, callsParent.getId(), conversationId); + PKG, 0, PKG, callsParent.getId(), false, conversationId); assertEquals(messagesParent.getId(), messagesChild.getParentChannelId()); assertEquals(conversationId, messagesChild.getConversationId()); @@ -5887,8 +5888,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.deleteConversationNotificationChannels(PKG, mUid, conversationId); assertNull(mBinderService.getConversationNotificationChannel( - PKG, 0, PKG, messagesParent.getId(), conversationId)); + PKG, 0, PKG, messagesParent.getId(), false, conversationId)); assertNull(mBinderService.getConversationNotificationChannel( - PKG, 0, PKG, callsParent.getId(), conversationId)); + PKG, 0, PKG, callsParent.getId(), false, conversationId)); } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 7173e0cb625c..6f1657441fb5 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -227,6 +227,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertEquals(expected.getLightColor(), actual.getLightColor()); assertEquals(expected.getParentChannelId(), actual.getParentChannelId()); assertEquals(expected.getConversationId(), actual.getConversationId()); + assertEquals(expected.isDemoted(), actual.isDemoted()); } private void compareChannelsParentChild(NotificationChannel parent, @@ -354,6 +355,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { channel2.setGroup(ncg.getId()); channel2.setVibrationPattern(new long[]{100, 67, 145, 156}); channel2.setLightColor(Color.BLUE); + channel2.setConversationId("id1", "conversation"); + channel2.setDemoted(true); mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true); mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg2, true); @@ -2822,8 +2825,20 @@ public class PreferencesHelperTest extends UiServiceTestCase { friend.setConversationId(parent.getId(), conversationId); mHelper.createNotificationChannel(PKG_O, UID_O, friend, true, false); - compareChannelsParentChild(parent, mHelper.getNotificationChannel( - PKG_O, UID_O, parent.getId(), conversationId, false), conversationId); + compareChannelsParentChild(parent, mHelper.getConversationNotificationChannel( + PKG_O, UID_O, parent.getId(), conversationId, false, false), conversationId); + } + + @Test + public void testGetNotificationChannel_conversationProvidedByNotCustomizedYet() { + String conversationId = "friend"; + + NotificationChannel parent = + new NotificationChannel("parent", "messages", IMPORTANCE_DEFAULT); + mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false); + + compareChannels(parent, mHelper.getConversationNotificationChannel( + PKG_O, UID_O, parent.getId(), conversationId, true, false)); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java index 399cf49ac06f..135d00586329 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT; import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; @@ -44,6 +45,7 @@ import android.testing.DexmakerShareClassLoaderRule; import androidx.test.filters.SmallTest; +import com.android.internal.app.BlockedAppActivity; import com.android.internal.app.HarmfulAppWarningActivity; import com.android.internal.app.SuspendedAppActivity; import com.android.internal.app.UnlaunchableAppActivity; @@ -105,6 +107,8 @@ public class ActivityStartInterceptorTest { private PackageManagerService mPackageManager; @Mock private ActivityManagerInternal mAmInternal; + @Mock + private LockTaskController mLockTaskController; private ActivityStartInterceptor mInterceptor; private ActivityInfo mAInfo = new ActivityInfo(); @@ -145,6 +149,13 @@ public class ActivityStartInterceptorTest { when(mPackageManager.getHarmfulAppWarning(TEST_PACKAGE_NAME, TEST_USER_ID)) .thenReturn(null); + // Mock LockTaskController + mAInfo.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT; + when(mService.getLockTaskController()).thenReturn(mLockTaskController); + when(mLockTaskController.isActivityAllowed( + TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT)) + .thenReturn(true); + // Initialise activity info mAInfo.applicationInfo = new ApplicationInfo(); mAInfo.packageName = mAInfo.applicationInfo.packageName = TEST_PACKAGE_NAME; @@ -196,6 +207,18 @@ public class ActivityStartInterceptorTest { } @Test + public void testInterceptLockTaskModeViolationPackage() { + when(mLockTaskController.isActivityAllowed( + TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT)) + .thenReturn(false); + + assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null)); + + assertTrue(BlockedAppActivity.createIntent(TEST_USER_ID, TEST_PACKAGE_NAME) + .filterEquals(mInterceptor.mIntent)); + } + + @Test public void testInterceptQuietProfile() { // GIVEN that the user the activity is starting as is currently in quiet mode when(mUserManager.isQuietModeEnabled(eq(UserHandle.of(TEST_USER_ID)))).thenReturn(true); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java index 079c49f060ce..d22502db69e4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java @@ -27,6 +27,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSess import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -153,6 +154,19 @@ public class ActivityTaskManagerServiceTests extends ActivityTestsBase { } @Test + public void testContainerChanges() { + removeGlobalMinSizeRestriction(); + final ActivityStack stack = new StackBuilder(mRootWindowContainer) + .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); + final Task task = stack.getTopMostTask(); + WindowContainerTransaction t = new WindowContainerTransaction(); + assertTrue(task.isFocusable()); + t.setFocusable(stack.mRemoteToken, false); + mService.applyContainerTransaction(t); + assertFalse(task.isFocusable()); + } + + @Test public void testDisplayWindowListener() { final ArrayList<Integer> added = new ArrayList<>(); final ArrayList<Integer> changed = new ArrayList<>(); diff --git a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java new file mode 100644 index 000000000000..032edde61bec --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java @@ -0,0 +1,161 @@ +/* + * 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 com.android.server.wm; + +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import android.platform.test.annotations.Presubmit; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * This file tests WM setting the priority on windows that is used in SF to determine at what + * frame rate the Display should run. Any changes to the algorithm should be reflected in these + * tests. + * + * Build/Install/Run: atest FrameRateSelectionPriority + */ +@Presubmit +@RunWith(WindowTestRunner.class) +public class FrameRateSelectionPriorityTests extends WindowTestsBase { + + @Test + public void basicTest() { + final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow"); + assertNotNull("Window state is created", appWindow); + assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET); + + appWindow.updateFrameRateSelectionPriorityIfNeeded(); + // Priority doesn't change. + assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET); + + // Call the function a few times. + appWindow.updateFrameRateSelectionPriorityIfNeeded(); + appWindow.updateFrameRateSelectionPriorityIfNeeded(); + + // Since nothing changed in the priority state, the transaction should not be updating. + verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority( + appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET); + } + + @Test + public void testApplicationInFocusWithoutModeId() { + final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow"); + assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET); + assertEquals(appWindow.getDisplayContent().getDisplayPolicy().getRefreshRatePolicy() + .getPreferredModeId(appWindow), 0); + + appWindow.updateFrameRateSelectionPriorityIfNeeded(); + // Priority stays MAX_VALUE. + assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET); + verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority( + appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET); + + // Application is in focus. + appWindow.mToken.mDisplayContent.mCurrentFocus = appWindow; + appWindow.updateFrameRateSelectionPriorityIfNeeded(); + // Priority changes to 1. + assertEquals(appWindow.mFrameRateSelectionPriority, 1); + verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority( + appWindow.getSurfaceControl(), 1); + } + + @Test + public void testApplicationInFocusWithModeId() { + final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow"); + assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET); + + // Application is in focus. + appWindow.mToken.mDisplayContent.mCurrentFocus = appWindow; + appWindow.updateFrameRateSelectionPriorityIfNeeded(); + // Priority changes. + assertEquals(appWindow.mFrameRateSelectionPriority, 1); + // Update the mode ID to a requested number. + appWindow.mAttrs.preferredDisplayModeId = 1; + appWindow.updateFrameRateSelectionPriorityIfNeeded(); + // Priority changes. + assertEquals(appWindow.mFrameRateSelectionPriority, 0); + + // Remove the mode ID request. + appWindow.mAttrs.preferredDisplayModeId = 0; + appWindow.updateFrameRateSelectionPriorityIfNeeded(); + // Priority changes. + assertEquals(appWindow.mFrameRateSelectionPriority, 1); + + // Verify we called actions on Transactions correctly. + verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority( + appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET); + verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority( + appWindow.getSurfaceControl(), 0); + verify(appWindow.getPendingTransaction(), times(2)).setFrameRateSelectionPriority( + appWindow.getSurfaceControl(), 1); + } + + @Test + public void testApplicationNotInFocusWithModeId() { + final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow"); + assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET); + + final WindowState inFocusWindow = createWindow(null, TYPE_APPLICATION, "inFocus"); + appWindow.mToken.mDisplayContent.mCurrentFocus = inFocusWindow; + + appWindow.updateFrameRateSelectionPriorityIfNeeded(); + // The window is not in focus. + assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET); + + // Update the mode ID to a requested number. + appWindow.mAttrs.preferredDisplayModeId = 1; + appWindow.updateFrameRateSelectionPriorityIfNeeded(); + // Priority changes. + assertEquals(appWindow.mFrameRateSelectionPriority, 2); + + verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority( + appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET); + verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority( + appWindow.getSurfaceControl(), 2); + } + + @Test + public void testApplicationNotInFocusWithoutModeId() { + final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow"); + assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET); + + final WindowState inFocusWindow = createWindow(null, TYPE_APPLICATION, "inFocus"); + appWindow.mToken.mDisplayContent.mCurrentFocus = inFocusWindow; + + appWindow.updateFrameRateSelectionPriorityIfNeeded(); + // The window is not in focus. + assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET); + + // Make sure that the mode ID is not set. + appWindow.mAttrs.preferredDisplayModeId = 0; + appWindow.updateFrameRateSelectionPriorityIfNeeded(); + // Priority doesn't change. + assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET); + + verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority( + appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java index 039ff604f3f1..75ec53d69a71 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java @@ -29,6 +29,9 @@ import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME; import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD; import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NONE; import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS; +import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS; +import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT; +import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER; import static android.os.Process.SYSTEM_UID; import static android.telecom.TelecomManager.EMERGENCY_DIALER_COMPONENT; @@ -693,6 +696,38 @@ public class LockTaskControllerTest { assertTrue((StatusBarManager.DISABLE2_QUICK_SETTINGS & flags.second) != 0); } + @Test + public void testIsActivityAllowed() { + // WHEN lock task mode is not enabled + assertTrue(mLockTaskController.isActivityAllowed( + TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT)); + + // WHEN lock task mode is enabled + Task tr = getTask(Task.LOCK_TASK_AUTH_WHITELISTED); + mLockTaskController.startLockTaskMode(tr, false, TEST_UID); + + + // package with LOCK_TASK_LAUNCH_MODE_ALWAYS should always be allowed + assertTrue(mLockTaskController.isActivityAllowed( + TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_ALWAYS)); + + // unwhitelisted package should not be allowed + assertFalse(mLockTaskController.isActivityAllowed( + TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT)); + + // update the whitelist + String[] whitelist = new String[] { TEST_PACKAGE_NAME }; + mLockTaskController.updateLockTaskPackages(TEST_USER_ID, whitelist); + + // whitelisted package should be allowed + assertTrue(mLockTaskController.isActivityAllowed( + TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT)); + + // package with LOCK_TASK_LAUNCH_MODE_NEVER should never be allowed + assertFalse(mLockTaskController.isActivityAllowed( + TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_NEVER)); + } + private Task getTask(int lockTaskAuth) { return getTask(TEST_PACKAGE_NAME, lockTaskAuth); } diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java index 04d79ca5ddeb..ea8d0821dae3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java @@ -253,12 +253,12 @@ public class RootActivityContainerTests extends ActivityTestsBase { // Under split screen primary we should be focusable when not minimized mRootWindowContainer.setDockedStackMinimized(false); - assertTrue(stack.isFocusable()); + assertTrue(stack.isTopActivityFocusable()); assertTrue(activity.isFocusable()); // Under split screen primary we should not be focusable when minimized mRootWindowContainer.setDockedStackMinimized(true); - assertFalse(stack.isFocusable()); + assertFalse(stack.isTopActivityFocusable()); assertFalse(activity.isFocusable()); final ActivityStack pinnedStack = mRootWindowContainer.getDefaultDisplay().createStack( @@ -267,19 +267,19 @@ public class RootActivityContainerTests extends ActivityTestsBase { .setStack(pinnedStack).build(); // We should not be focusable when in pinned mode - assertFalse(pinnedStack.isFocusable()); + assertFalse(pinnedStack.isTopActivityFocusable()); assertFalse(pinnedActivity.isFocusable()); // Add flag forcing focusability. pinnedActivity.info.flags |= FLAG_ALWAYS_FOCUSABLE; // We should not be focusable when in pinned mode - assertTrue(pinnedStack.isFocusable()); + assertTrue(pinnedStack.isTopActivityFocusable()); assertTrue(pinnedActivity.isFocusable()); // Without the overridding activity, stack should not be focusable. pinnedStack.removeChild(pinnedActivity.getTask(), "testFocusability"); - assertFalse(pinnedStack.isFocusable()); + assertFalse(pinnedStack.isTopActivityFocusable()); } /** 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 64db89706d39..890e4ab90ce9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -391,6 +391,33 @@ public class SizeCompatTests extends ActivityTestsBase { assertEquals(null, compatTokens.get(0)); } + @Test + public void testShouldUseSizeCompatModeOnResizableTask() { + setUpApp(new TestDisplayContent.Builder(mService, 1000, 2500).build()); + + // Make the task root resizable. + mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE; + + // Create a size compat activity on the same task. + final ActivityRecord activity = new ActivityBuilder(mService) + .setTask(mTask) + .setResizeMode(ActivityInfo.RESIZE_MODE_UNRESIZEABLE) + .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) + .build(); + assertTrue(activity.shouldUseSizeCompatMode()); + + // The non-resizable activity should not be size compat because it is on a resizable task + // in multi-window mode. + mStack.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM); + assertFalse(activity.shouldUseSizeCompatMode()); + + // The non-resizable activity should not be size compat because the display support + // changing windowing mode from fullscreen to freeform. + mStack.mDisplayContent.setDisplayWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM); + mStack.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN); + assertFalse(activity.shouldUseSizeCompatMode()); + } + /** * Setup {@link #mActivity} as a size-compat-mode-able activity with fixed aspect and/or * orientation. 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 f5d08dcfcb77..eda1fb8839a5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java +++ b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java @@ -250,4 +250,9 @@ public class StubTransaction extends SurfaceControl.Transaction { return this; } + @Override + public SurfaceControl.Transaction setFrameRateSelectionPriority(SurfaceControl sc, + int priority) { + return this; + } } diff --git a/services/usage/java/com/android/server/usage/StorageStatsManagerInternal.java b/services/usage/java/com/android/server/usage/StorageStatsManagerInternal.java new file mode 100644 index 000000000000..a5325482605d --- /dev/null +++ b/services/usage/java/com/android/server/usage/StorageStatsManagerInternal.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.usage; + +import android.annotation.NonNull; +import android.annotation.UserIdInt; +import android.content.pm.PackageStats; + +/** + * StorageStatsManager local system service interface. + * + * Only for use within the system server. + */ +public abstract class StorageStatsManagerInternal { + /** + * Class used to augment {@link PackageStats} with the data stored by the system on + * behalf of apps in system specific directories + * ({@link android.os.Environment#getDataSystemDirectory}, + * {@link android.os.Environment#getDataSystemCeDirectory}, etc). + */ + public interface StorageStatsAugmenter { + void augmentStatsForPackage(@NonNull PackageStats stats, + @NonNull String packageName, @UserIdInt int userId, + @NonNull String callingPackage); + void augmentStatsForUid(@NonNull PackageStats stats, int uid, + @NonNull String callingPackage); + void augmentStatsForUser(@NonNull PackageStats stats, @UserIdInt int userId, + @NonNull String callingPackage); + } + + /** + * Register a {@link StorageStatsAugmenter}. + * + * @param augmenter the {@link StorageStatsAugmenter} object to be registered. + * @param tag the identifier to be used for debugging in logs/trace. + */ + public abstract void registerStorageStatsAugmenter(@NonNull StorageStatsAugmenter augmenter, + @NonNull String tag); +} diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java index 531a93117752..18b640ff6bf5 100644 --- a/services/usage/java/com/android/server/usage/StorageStatsService.java +++ b/services/usage/java/com/android/server/usage/StorageStatsService.java @@ -18,6 +18,7 @@ package com.android.server.usage; import static com.android.internal.util.ArrayUtils.defeatNullable; import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; +import static com.android.server.usage.StorageStatsManagerInternal.StorageStatsAugmenter; import android.annotation.NonNull; import android.annotation.Nullable; @@ -46,6 +47,7 @@ import android.os.Message; import android.os.ParcelableException; import android.os.StatFs; import android.os.SystemProperties; +import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.os.storage.CrateInfo; @@ -58,6 +60,7 @@ import android.text.TextUtils; import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.DataUnit; +import android.util.Pair; import android.util.Slog; import android.util.SparseLongArray; @@ -77,6 +80,8 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; public class StorageStatsService extends IStorageStatsManager.Stub { private static final String TAG = "StorageStatsService"; @@ -111,6 +116,9 @@ public class StorageStatsService extends IStorageStatsManager.Stub { private final Installer mInstaller; private final H mHandler; + private final CopyOnWriteArrayList<Pair<String, StorageStatsAugmenter>> + mStorageStatsAugmenters = new CopyOnWriteArrayList<>(); + public StorageStatsService(Context context) { mContext = Preconditions.checkNotNull(context); mAppOps = Preconditions.checkNotNull(context.getSystemService(AppOpsManager.class)); @@ -139,6 +147,8 @@ public class StorageStatsService extends IStorageStatsManager.Stub { } } }); + + LocalServices.addService(StorageStatsManagerInternal.class, new LocalService()); } private void invalidateMounts() { @@ -300,6 +310,12 @@ public class StorageStatsService extends IStorageStatsManager.Stub { } catch (InstallerException e) { throw new ParcelableException(new IOException(e.getMessage())); } + if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) { + forEachStorageStatsAugmenter((storageStatsAugmenter) -> { + storageStatsAugmenter.augmentStatsForPackage(stats, + packageName, userId, callingPackage); + }, "queryStatsForPackage"); + } return translate(stats); } } @@ -353,6 +369,12 @@ public class StorageStatsService extends IStorageStatsManager.Stub { } catch (InstallerException e) { throw new ParcelableException(new IOException(e.getMessage())); } + + if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) { + forEachStorageStatsAugmenter((storageStatsAugmenter) -> { + storageStatsAugmenter.augmentStatsForUid(stats, uid, callingPackage); + }, "queryStatsForUid"); + } return translate(stats); } @@ -379,6 +401,12 @@ public class StorageStatsService extends IStorageStatsManager.Stub { } catch (InstallerException e) { throw new ParcelableException(new IOException(e.getMessage())); } + + if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) { + forEachStorageStatsAugmenter((storageStatsAugmenter) -> { + storageStatsAugmenter.augmentStatsForUser(stats, userId, callingPackage); + }, "queryStatsForUser"); + } return translate(stats); } @@ -705,4 +733,29 @@ public class StorageStatsService extends IStorageStatsManager.Stub { throw new ParcelableException(new IOException(e.getMessage())); } } + + void forEachStorageStatsAugmenter(@NonNull Consumer<StorageStatsAugmenter> consumer, + @NonNull String queryTag) { + for (int i = 0, count = mStorageStatsAugmenters.size(); i < count; ++i) { + final Pair<String, StorageStatsAugmenter> pair = mStorageStatsAugmenters.get(i); + final String augmenterTag = pair.first; + final StorageStatsAugmenter storageStatsAugmenter = pair.second; + + Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, queryTag + ":" + augmenterTag); + try { + consumer.accept(storageStatsAugmenter); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); + } + } + } + + private class LocalService extends StorageStatsManagerInternal { + @Override + public void registerStorageStatsAugmenter( + @NonNull StorageStatsAugmenter storageStatsAugmenter, + @NonNull String tag) { + mStorageStatsAugmenters.add(Pair.create(tag, storageStatsAugmenter)); + } + } } diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 8397aa485595..b8cd37860284 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -2069,6 +2069,13 @@ public class UsageStatsService extends SystemService implements } @Override + public UsageEvents queryEventsForUser(int userId, long beginTime, long endTime, + boolean shouldObfuscateInstantApps) { + return UsageStatsService.this.queryEvents( + userId, beginTime, endTime, shouldObfuscateInstantApps); + } + + @Override public void setLastJobRunTime(String packageName, int userId, long elapsedRealtime) { mAppStandby.setLastJobRunTime(packageName, userId, elapsedRealtime); } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java index f7cd6a3d2245..99065854445a 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java @@ -21,12 +21,10 @@ import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; -import android.hardware.soundtrigger.SoundTrigger; import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel; -import android.text.TextUtils; import android.util.Slog; -import java.util.Locale; +import java.io.PrintWriter; import java.util.UUID; /** @@ -39,7 +37,7 @@ public class SoundTriggerDbHelper extends SQLiteOpenHelper { static final boolean DBG = false; private static final String NAME = "st_sound_model.db"; - private static final int VERSION = 1; + private static final int VERSION = 2; // Sound trigger-based sound models. public static interface GenericSoundModelContract { @@ -47,15 +45,16 @@ public class SoundTriggerDbHelper extends SQLiteOpenHelper { public static final String KEY_MODEL_UUID = "model_uuid"; public static final String KEY_VENDOR_UUID = "vendor_uuid"; public static final String KEY_DATA = "data"; + public static final String KEY_MODEL_VERSION = "model_version"; } - // Table Create Statement for the sound trigger table private static final String CREATE_TABLE_ST_SOUND_MODEL = "CREATE TABLE " + GenericSoundModelContract.TABLE + "(" + GenericSoundModelContract.KEY_MODEL_UUID + " TEXT PRIMARY KEY," + GenericSoundModelContract.KEY_VENDOR_UUID + " TEXT," - + GenericSoundModelContract.KEY_DATA + " BLOB" + " )"; + + GenericSoundModelContract.KEY_DATA + " BLOB," + + GenericSoundModelContract.KEY_MODEL_VERSION + " INTEGER" + " )"; public SoundTriggerDbHelper(Context context) { @@ -70,9 +69,13 @@ public class SoundTriggerDbHelper extends SQLiteOpenHelper { @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - // TODO: For now, drop older tables and recreate new ones. - db.execSQL("DROP TABLE IF EXISTS " + GenericSoundModelContract.TABLE); - onCreate(db); + if (oldVersion == 1) { + // In version 2, a model version number was added. + Slog.d(TAG, "Adding model version column"); + db.execSQL("ALTER TABLE " + GenericSoundModelContract.TABLE + " ADD COLUMN " + + GenericSoundModelContract.KEY_MODEL_VERSION + " INTEGER DEFAULT -1"); + oldVersion++; + } } /** @@ -86,6 +89,7 @@ public class SoundTriggerDbHelper extends SQLiteOpenHelper { values.put(GenericSoundModelContract.KEY_MODEL_UUID, soundModel.uuid.toString()); values.put(GenericSoundModelContract.KEY_VENDOR_UUID, soundModel.vendorUuid.toString()); values.put(GenericSoundModelContract.KEY_DATA, soundModel.data); + values.put(GenericSoundModelContract.KEY_MODEL_VERSION, soundModel.version); try { return db.insertWithOnConflict(GenericSoundModelContract.TABLE, null, values, @@ -113,8 +117,10 @@ public class SoundTriggerDbHelper extends SQLiteOpenHelper { GenericSoundModelContract.KEY_DATA)); String vendor_uuid = c.getString( c.getColumnIndex(GenericSoundModelContract.KEY_VENDOR_UUID)); + int version = c.getInt( + c.getColumnIndex(GenericSoundModelContract.KEY_MODEL_VERSION)); return new GenericSoundModel(model_uuid, UUID.fromString(vendor_uuid), - data); + data, version); } while (c.moveToNext()); } } finally { @@ -142,4 +148,48 @@ public class SoundTriggerDbHelper extends SQLiteOpenHelper { } } } + + public void dump(PrintWriter pw) { + synchronized(this) { + String selectQuery = "SELECT * FROM " + GenericSoundModelContract.TABLE; + SQLiteDatabase db = getReadableDatabase(); + Cursor c = db.rawQuery(selectQuery, null); + try { + pw.println(" Enrolled GenericSoundModels:"); + if (c.moveToFirst()) { + String[] columnNames = c.getColumnNames(); + do { + for (String name : columnNames) { + int colNameIndex = c.getColumnIndex(name); + int type = c.getType(colNameIndex); + switch (type) { + case Cursor.FIELD_TYPE_STRING: + pw.printf(" %s: %s\n", name, + c.getString(colNameIndex)); + break; + case Cursor.FIELD_TYPE_BLOB: + pw.printf(" %s: data blob\n", name); + break; + case Cursor.FIELD_TYPE_INTEGER: + pw.printf(" %s: %d\n", name, + c.getInt(colNameIndex)); + break; + case Cursor.FIELD_TYPE_FLOAT: + pw.printf(" %s: %f\n", name, + c.getFloat(colNameIndex)); + break; + case Cursor.FIELD_TYPE_NULL: + pw.printf(" %s: null\n", name); + break; + } + } + pw.println(); + } while (c.moveToNext()); + } + } finally { + c.close(); + db.close(); + } + } + } } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java index bde2cfd52c0f..7099c0935ee9 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java @@ -756,7 +756,9 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { return; } - model.setStopped(); + if (event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) { + model.setStopped(); + } try { callback.onGenericSoundTriggerDetected((GenericRecognitionEvent) event); @@ -900,7 +902,9 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { return; } - modelData.setStopped(); + if (event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) { + modelData.setStopped(); + } try { modelData.getCallback().onKeyphraseDetected((KeyphraseRecognitionEvent) event); diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java index e37755bddcaa..767010ae821a 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java @@ -1495,6 +1495,9 @@ public class SoundTriggerService extends SystemService { // log sEventLogger.dump(pw); + // enrolled models + mDbHelper.dump(pw); + // stats mSoundModelStatTracker.dump(pw); } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java index dd7b5a8752e5..c58b6da64baa 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java @@ -27,6 +27,7 @@ import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; import android.text.TextUtils; import android.util.Slog; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -43,7 +44,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { static final boolean DBG = false; private static final String NAME = "sound_model.db"; - private static final int VERSION = 6; + private static final int VERSION = 7; public static interface SoundModelContract { public static final String TABLE = "sound_model"; @@ -56,6 +57,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { public static final String KEY_LOCALE = "locale"; public static final String KEY_HINT_TEXT = "hint_text"; public static final String KEY_USERS = "users"; + public static final String KEY_MODEL_VERSION = "model_version"; } // Table Create Statement @@ -70,6 +72,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { + SoundModelContract.KEY_LOCALE + " TEXT," + SoundModelContract.KEY_HINT_TEXT + " TEXT," + SoundModelContract.KEY_USERS + " TEXT," + + SoundModelContract.KEY_MODEL_VERSION + " INTEGER," + "PRIMARY KEY (" + SoundModelContract.KEY_KEYPHRASE_ID + "," + SoundModelContract.KEY_LOCALE + "," + SoundModelContract.KEY_USERS + ")" @@ -138,6 +141,13 @@ public class DatabaseHelper extends SQLiteOpenHelper { } oldVersion++; } + if (oldVersion == 6) { + // In version 7, a model version number was added. + Slog.d(TAG, "Adding model version column"); + db.execSQL("ALTER TABLE " + SoundModelContract.TABLE + " ADD COLUMN " + + SoundModelContract.KEY_MODEL_VERSION + " INTEGER DEFAULT -1"); + oldVersion++; + } } /** @@ -155,6 +165,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { } values.put(SoundModelContract.KEY_TYPE, SoundTrigger.SoundModel.TYPE_KEYPHRASE); values.put(SoundModelContract.KEY_DATA, soundModel.data); + values.put(SoundModelContract.KEY_MODEL_VERSION, soundModel.version); if (soundModel.keyphrases != null && soundModel.keyphrases.length == 1) { values.put(SoundModelContract.KEY_KEYPHRASE_ID, soundModel.keyphrases[0].id); @@ -250,6 +261,8 @@ public class DatabaseHelper extends SQLiteOpenHelper { c.getColumnIndex(SoundModelContract.KEY_LOCALE)); String text = c.getString( c.getColumnIndex(SoundModelContract.KEY_HINT_TEXT)); + int version = c.getInt( + c.getColumnIndex(SoundModelContract.KEY_MODEL_VERSION)); // Only add keyphrases meant for the current user. if (users == null) { @@ -282,7 +295,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { vendorUuid = UUID.fromString(vendorUuidString); } KeyphraseSoundModel model = new KeyphraseSoundModel( - UUID.fromString(modelUuid), vendorUuid, data, keyphrases); + UUID.fromString(modelUuid), vendorUuid, data, keyphrases, version); if (DBG) { Slog.d(TAG, "Found SoundModel for the given keyphrase/locale/user: " + model); @@ -325,6 +338,10 @@ public class DatabaseHelper extends SQLiteOpenHelper { return users; } + /** + * SoundModelRecord is no longer used, and it should only be used on database migration. + * This class does not need to be modified when modifying the database scheme. + */ private static class SoundModelRecord { public final String modelUuid; public final String vendorUuid; @@ -413,4 +430,48 @@ public class DatabaseHelper extends SQLiteOpenHelper { return a == b; } } + + public void dump(PrintWriter pw) { + synchronized(this) { + String selectQuery = "SELECT * FROM " + SoundModelContract.TABLE; + SQLiteDatabase db = getReadableDatabase(); + Cursor c = db.rawQuery(selectQuery, null); + try { + pw.println(" Enrolled KeyphraseSoundModels:"); + if (c.moveToFirst()) { + String[] columnNames = c.getColumnNames(); + do { + for (String name : columnNames) { + int colNameIndex = c.getColumnIndex(name); + int type = c.getType(colNameIndex); + switch (type) { + case Cursor.FIELD_TYPE_STRING: + pw.printf(" %s: %s\n", name, + c.getString(colNameIndex)); + break; + case Cursor.FIELD_TYPE_BLOB: + pw.printf(" %s: data blob\n", name); + break; + case Cursor.FIELD_TYPE_INTEGER: + pw.printf(" %s: %d\n", name, + c.getInt(colNameIndex)); + break; + case Cursor.FIELD_TYPE_FLOAT: + pw.printf(" %s: %f\n", name, + c.getFloat(colNameIndex)); + break; + case Cursor.FIELD_TYPE_NULL: + pw.printf(" %s: null\n", name); + break; + } + } + pw.println(); + } while (c.moveToNext()); + } + } finally { + c.close(); + db.close(); + } + } + } } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 6b95f0fe5350..506c67e12528 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -1347,6 +1347,7 @@ public class VoiceInteractionManagerService extends SystemService { pw.println(" mCurUserUnlocked: " + mCurUserUnlocked); pw.println(" mCurUserSupported: " + mCurUserSupported); dumpSupportedUsers(pw, " "); + mDbHelper.dump(pw); if (mImpl == null) { pw.println(" (No active implementation)"); return; diff --git a/telecomm/TEST_MAPPING b/telecomm/TEST_MAPPING new file mode 100644 index 000000000000..d58566673eec --- /dev/null +++ b/telecomm/TEST_MAPPING @@ -0,0 +1,29 @@ +{ + "presubmit": [ + { + "name": "TeleServiceTests", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "TelecomUnitTests", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "TelephonyProviderTests", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + } + ] +} + diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index a8852a849604..826a89eb38bb 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -568,6 +568,7 @@ public final class Call { private final Bundle mExtras; private final Bundle mIntentExtras; private final long mCreationTimeMillis; + private final String mContactDisplayName; private final @CallDirection int mCallDirection; private final @Connection.VerificationStatus int mCallerNumberVerificationStatus; @@ -872,6 +873,17 @@ public final class Call { } /** + * Returns the name of the caller on the remote end, as derived from a + * {@link android.provider.ContactsContract} lookup of the call's handle. + * @return The name of the caller, or {@code null} if the lookup is not yet complete, if + * there's no contacts entry for the caller, or if the {@link InCallService} does + * not hold the {@link android.Manifest.permission#READ_CONTACTS} permission. + */ + public @Nullable String getContactDisplayName() { + return mContactDisplayName; + } + + /** * Indicates whether the call is an incoming or outgoing call. * @return The call's direction. */ @@ -909,6 +921,7 @@ public final class Call { areBundlesEqual(mExtras, d.mExtras) && areBundlesEqual(mIntentExtras, d.mIntentExtras) && Objects.equals(mCreationTimeMillis, d.mCreationTimeMillis) && + Objects.equals(mContactDisplayName, d.mContactDisplayName) && Objects.equals(mCallDirection, d.mCallDirection) && Objects.equals(mCallerNumberVerificationStatus, d.mCallerNumberVerificationStatus); @@ -933,6 +946,7 @@ public final class Call { mExtras, mIntentExtras, mCreationTimeMillis, + mContactDisplayName, mCallDirection, mCallerNumberVerificationStatus); } @@ -955,6 +969,7 @@ public final class Call { Bundle extras, Bundle intentExtras, long creationTimeMillis, + String contactDisplayName, int callDirection, int callerNumberVerificationStatus) { mTelecomCallId = telecomCallId; @@ -973,6 +988,7 @@ public final class Call { mExtras = extras; mIntentExtras = intentExtras; mCreationTimeMillis = creationTimeMillis; + mContactDisplayName = contactDisplayName; mCallDirection = callDirection; mCallerNumberVerificationStatus = callerNumberVerificationStatus; } @@ -996,6 +1012,7 @@ public final class Call { parcelableCall.getExtras(), parcelableCall.getIntentExtras(), parcelableCall.getCreationTimeMillis(), + parcelableCall.getContactDisplayName(), parcelableCall.getCallDirection(), parcelableCall.getCallerNumberVerificationStatus()); } @@ -1445,6 +1462,7 @@ public final class Call { private boolean mChildrenCached; private String mParentId = null; + private String mActiveGenericConferenceChild = null; private int mState; private List<String> mCannedTextResponses = null; private String mCallingPackage; @@ -1943,6 +1961,20 @@ public final class Call { } /** + * Returns the child {@link Call} in a generic conference that is currently active. + * For calls that are not generic conferences, or when the generic conference has more than + * 2 children, returns {@code null}. + * @see Details#PROPERTY_GENERIC_CONFERENCE + * @return The active child call. + */ + public @Nullable Call getGenericConferenceActiveChildCall() { + if (mActiveGenericConferenceChild != null) { + return mPhone.internalGetCallByTelecomId(mActiveGenericConferenceChild); + } + return null; + } + + /** * Obtains a list of canned, pre-configured message responses to present to the user as * ways of rejecting this {@code Call} using via a text message. * @@ -2190,6 +2222,13 @@ public final class Call { mChildrenCached = false; } + String activeChildCallId = parcelableCall.getActiveChildCallId(); + boolean activeChildChanged = !Objects.equals(activeChildCallId, + mActiveGenericConferenceChild); + if (activeChildChanged) { + mActiveGenericConferenceChild = activeChildCallId; + } + List<String> conferenceableCallIds = parcelableCall.getConferenceableCallIds(); List<Call> conferenceableCalls = new ArrayList<Call>(conferenceableCallIds.size()); for (String otherId : conferenceableCallIds) { @@ -2249,7 +2288,7 @@ public final class Call { if (parentChanged) { fireParentChanged(getParent()); } - if (childrenChanged) { + if (childrenChanged || activeChildChanged) { fireChildrenChanged(getChildren()); } if (isRttChanged) { diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java index be4e2f4c65e1..415a817b58d5 100644 --- a/telecomm/java/android/telecom/ParcelableCall.java +++ b/telecomm/java/android/telecom/ParcelableCall.java @@ -16,6 +16,7 @@ package android.telecom; +import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.net.Uri; import android.os.Build; @@ -36,6 +37,265 @@ import java.util.List; * @hide */ public final class ParcelableCall implements Parcelable { + + public static class ParcelableCallBuilder { + private String mId; + private int mState; + private DisconnectCause mDisconnectCause; + private List<String> mCannedSmsResponses; + private int mCapabilities; + private int mProperties; + private int mSupportedAudioRoutes; + private long mConnectTimeMillis; + private Uri mHandle; + private int mHandlePresentation; + private String mCallerDisplayName; + private int mCallerDisplayNamePresentation; + private GatewayInfo mGatewayInfo; + private PhoneAccountHandle mAccountHandle; + private boolean mIsVideoCallProviderChanged; + private IVideoProvider mVideoCallProvider; + private boolean mIsRttCallChanged; + private ParcelableRttCall mRttCall; + private String mParentCallId; + private List<String> mChildCallIds; + private StatusHints mStatusHints; + private int mVideoState; + private List<String> mConferenceableCallIds; + private Bundle mIntentExtras; + private Bundle mExtras; + private long mCreationTimeMillis; + private int mCallDirection; + private int mCallerNumberVerificationStatus; + private String mContactDisplayName; + private String mActiveChildCallId; + + public ParcelableCallBuilder setId(String id) { + mId = id; + return this; + } + + public ParcelableCallBuilder setState(int state) { + mState = state; + return this; + } + + public ParcelableCallBuilder setDisconnectCause(DisconnectCause disconnectCause) { + mDisconnectCause = disconnectCause; + return this; + } + + public ParcelableCallBuilder setCannedSmsResponses(List<String> cannedSmsResponses) { + mCannedSmsResponses = cannedSmsResponses; + return this; + } + + public ParcelableCallBuilder setCapabilities(int capabilities) { + mCapabilities = capabilities; + return this; + } + + public ParcelableCallBuilder setProperties(int properties) { + mProperties = properties; + return this; + } + + public ParcelableCallBuilder setSupportedAudioRoutes(int supportedAudioRoutes) { + mSupportedAudioRoutes = supportedAudioRoutes; + return this; + } + + public ParcelableCallBuilder setConnectTimeMillis(long connectTimeMillis) { + mConnectTimeMillis = connectTimeMillis; + return this; + } + + public ParcelableCallBuilder setHandle(Uri handle) { + mHandle = handle; + return this; + } + + public ParcelableCallBuilder setHandlePresentation(int handlePresentation) { + mHandlePresentation = handlePresentation; + return this; + } + + public ParcelableCallBuilder setCallerDisplayName(String callerDisplayName) { + mCallerDisplayName = callerDisplayName; + return this; + } + + public ParcelableCallBuilder setCallerDisplayNamePresentation( + int callerDisplayNamePresentation) { + mCallerDisplayNamePresentation = callerDisplayNamePresentation; + return this; + } + + public ParcelableCallBuilder setGatewayInfo(GatewayInfo gatewayInfo) { + mGatewayInfo = gatewayInfo; + return this; + } + + public ParcelableCallBuilder setAccountHandle(PhoneAccountHandle accountHandle) { + mAccountHandle = accountHandle; + return this; + } + + public ParcelableCallBuilder setIsVideoCallProviderChanged( + boolean isVideoCallProviderChanged) { + mIsVideoCallProviderChanged = isVideoCallProviderChanged; + return this; + } + + public ParcelableCallBuilder setVideoCallProvider(IVideoProvider videoCallProvider) { + mVideoCallProvider = videoCallProvider; + return this; + } + + public ParcelableCallBuilder setIsRttCallChanged(boolean isRttCallChanged) { + mIsRttCallChanged = isRttCallChanged; + return this; + } + + public ParcelableCallBuilder setRttCall(ParcelableRttCall rttCall) { + mRttCall = rttCall; + return this; + } + + public ParcelableCallBuilder setParentCallId(String parentCallId) { + mParentCallId = parentCallId; + return this; + } + + public ParcelableCallBuilder setChildCallIds(List<String> childCallIds) { + mChildCallIds = childCallIds; + return this; + } + + public ParcelableCallBuilder setStatusHints(StatusHints statusHints) { + mStatusHints = statusHints; + return this; + } + + public ParcelableCallBuilder setVideoState(int videoState) { + mVideoState = videoState; + return this; + } + + public ParcelableCallBuilder setConferenceableCallIds( + List<String> conferenceableCallIds) { + mConferenceableCallIds = conferenceableCallIds; + return this; + } + + public ParcelableCallBuilder setIntentExtras(Bundle intentExtras) { + mIntentExtras = intentExtras; + return this; + } + + public ParcelableCallBuilder setExtras(Bundle extras) { + mExtras = extras; + return this; + } + + public ParcelableCallBuilder setCreationTimeMillis(long creationTimeMillis) { + mCreationTimeMillis = creationTimeMillis; + return this; + } + + public ParcelableCallBuilder setCallDirection(int callDirection) { + mCallDirection = callDirection; + return this; + } + + public ParcelableCallBuilder setCallerNumberVerificationStatus( + int callerNumberVerificationStatus) { + mCallerNumberVerificationStatus = callerNumberVerificationStatus; + return this; + } + + public ParcelableCallBuilder setContactDisplayName(String contactDisplayName) { + mContactDisplayName = contactDisplayName; + return this; + } + + public ParcelableCallBuilder setActiveChildCallId(String activeChildCallId) { + mActiveChildCallId = activeChildCallId; + return this; + } + + public ParcelableCall createParcelableCall() { + return new ParcelableCall( + mId, + mState, + mDisconnectCause, + mCannedSmsResponses, + mCapabilities, + mProperties, + mSupportedAudioRoutes, + mConnectTimeMillis, + mHandle, + mHandlePresentation, + mCallerDisplayName, + mCallerDisplayNamePresentation, + mGatewayInfo, + mAccountHandle, + mIsVideoCallProviderChanged, + mVideoCallProvider, + mIsRttCallChanged, + mRttCall, + mParentCallId, + mChildCallIds, + mStatusHints, + mVideoState, + mConferenceableCallIds, + mIntentExtras, + mExtras, + mCreationTimeMillis, + mCallDirection, + mCallerNumberVerificationStatus, + mContactDisplayName, + mActiveChildCallId); + } + + public static ParcelableCallBuilder fromParcelableCall(ParcelableCall parcelableCall) { + ParcelableCallBuilder newBuilder = new ParcelableCallBuilder(); + newBuilder.mId = parcelableCall.mId; + newBuilder.mState = parcelableCall.mState; + newBuilder.mDisconnectCause = parcelableCall.mDisconnectCause; + newBuilder.mCannedSmsResponses = parcelableCall.mCannedSmsResponses; + newBuilder.mCapabilities = parcelableCall.mCapabilities; + newBuilder.mProperties = parcelableCall.mProperties; + newBuilder.mSupportedAudioRoutes = parcelableCall.mSupportedAudioRoutes; + newBuilder.mConnectTimeMillis = parcelableCall.mConnectTimeMillis; + newBuilder.mHandle = parcelableCall.mHandle; + newBuilder.mHandlePresentation = parcelableCall.mHandlePresentation; + newBuilder.mCallerDisplayName = parcelableCall.mCallerDisplayName; + newBuilder.mCallerDisplayNamePresentation = + parcelableCall.mCallerDisplayNamePresentation; + newBuilder.mGatewayInfo = parcelableCall.mGatewayInfo; + newBuilder.mAccountHandle = parcelableCall.mAccountHandle; + newBuilder.mIsVideoCallProviderChanged = parcelableCall.mIsVideoCallProviderChanged; + newBuilder.mVideoCallProvider = parcelableCall.mVideoCallProvider; + newBuilder.mIsRttCallChanged = parcelableCall.mIsRttCallChanged; + newBuilder.mRttCall = parcelableCall.mRttCall; + newBuilder.mParentCallId = parcelableCall.mParentCallId; + newBuilder.mChildCallIds = parcelableCall.mChildCallIds; + newBuilder.mStatusHints = parcelableCall.mStatusHints; + newBuilder.mVideoState = parcelableCall.mVideoState; + newBuilder.mConferenceableCallIds = parcelableCall.mConferenceableCallIds; + newBuilder.mIntentExtras = parcelableCall.mIntentExtras; + newBuilder.mExtras = parcelableCall.mExtras; + newBuilder.mCreationTimeMillis = parcelableCall.mCreationTimeMillis; + newBuilder.mCallDirection = parcelableCall.mCallDirection; + newBuilder.mCallerNumberVerificationStatus = + parcelableCall.mCallerNumberVerificationStatus; + newBuilder.mContactDisplayName = parcelableCall.mContactDisplayName; + newBuilder.mActiveChildCallId = parcelableCall.mActiveChildCallId; + return newBuilder; + } + } + private final String mId; private final int mState; private final DisconnectCause mDisconnectCause; @@ -65,6 +325,8 @@ public final class ParcelableCall implements Parcelable { private final long mCreationTimeMillis; private final int mCallDirection; private final int mCallerNumberVerificationStatus; + private final String mContactDisplayName; + private final String mActiveChildCallId; // Only valid for CDMA conferences public ParcelableCall( String id, @@ -94,7 +356,10 @@ public final class ParcelableCall implements Parcelable { Bundle extras, long creationTimeMillis, int callDirection, - int callerNumberVerificationStatus) { + int callerNumberVerificationStatus, + String contactDisplayName, + String activeChildCallId + ) { mId = id; mState = state; mDisconnectCause = disconnectCause; @@ -123,6 +388,8 @@ public final class ParcelableCall implements Parcelable { mCreationTimeMillis = creationTimeMillis; mCallDirection = callDirection; mCallerNumberVerificationStatus = callerNumberVerificationStatus; + mContactDisplayName = contactDisplayName; + mActiveChildCallId = activeChildCallId; } /** The unique ID of the call. */ @@ -332,6 +599,21 @@ public final class ParcelableCall implements Parcelable { return mCallerNumberVerificationStatus; } + /** + * @return the name of the remote party as derived from a contacts DB lookup. + */ + public @Nullable String getContactDisplayName() { + return mContactDisplayName; + } + + /** + * @return On a CDMA conference with two participants, returns the ID of the child call that's + * currently active. + */ + public @Nullable String getActiveChildCallId() { + return mActiveChildCallId; + } + /** Responsible for creating ParcelableCall objects for deserialized Parcels. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public static final @android.annotation.NonNull Parcelable.Creator<ParcelableCall> CREATOR = @@ -371,35 +653,40 @@ public final class ParcelableCall implements Parcelable { long creationTimeMillis = source.readLong(); int callDirection = source.readInt(); int callerNumberVerificationStatus = source.readInt(); - return new ParcelableCall( - id, - state, - disconnectCause, - cannedSmsResponses, - capabilities, - properties, - supportedAudioRoutes, - connectTimeMillis, - handle, - handlePresentation, - callerDisplayName, - callerDisplayNamePresentation, - gatewayInfo, - accountHandle, - isVideoCallProviderChanged, - videoCallProvider, - isRttCallChanged, - rttCall, - parentCallId, - childCallIds, - statusHints, - videoState, - conferenceableCallIds, - intentExtras, - extras, - creationTimeMillis, - callDirection, - callerNumberVerificationStatus); + String contactDisplayName = source.readString(); + String activeChildCallId = source.readString(); + return new ParcelableCallBuilder() + .setId(id) + .setState(state) + .setDisconnectCause(disconnectCause) + .setCannedSmsResponses(cannedSmsResponses) + .setCapabilities(capabilities) + .setProperties(properties) + .setSupportedAudioRoutes(supportedAudioRoutes) + .setConnectTimeMillis(connectTimeMillis) + .setHandle(handle) + .setHandlePresentation(handlePresentation) + .setCallerDisplayName(callerDisplayName) + .setCallerDisplayNamePresentation(callerDisplayNamePresentation) + .setGatewayInfo(gatewayInfo) + .setAccountHandle(accountHandle) + .setIsVideoCallProviderChanged(isVideoCallProviderChanged) + .setVideoCallProvider(videoCallProvider) + .setIsRttCallChanged(isRttCallChanged) + .setRttCall(rttCall) + .setParentCallId(parentCallId) + .setChildCallIds(childCallIds) + .setStatusHints(statusHints) + .setVideoState(videoState) + .setConferenceableCallIds(conferenceableCallIds) + .setIntentExtras(intentExtras) + .setExtras(extras) + .setCreationTimeMillis(creationTimeMillis) + .setCallDirection(callDirection) + .setCallerNumberVerificationStatus(callerNumberVerificationStatus) + .setContactDisplayName(contactDisplayName) + .setActiveChildCallId(activeChildCallId) + .createParcelableCall(); } @Override @@ -446,6 +733,8 @@ public final class ParcelableCall implements Parcelable { destination.writeLong(mCreationTimeMillis); destination.writeInt(mCallDirection); destination.writeInt(mCallerNumberVerificationStatus); + destination.writeString(mContactDisplayName); + destination.writeString(mActiveChildCallId); } @Override diff --git a/telephony/TEST_MAPPING b/telephony/TEST_MAPPING new file mode 100644 index 000000000000..d58566673eec --- /dev/null +++ b/telephony/TEST_MAPPING @@ -0,0 +1,29 @@ +{ + "presubmit": [ + { + "name": "TeleServiceTests", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "TelecomUnitTests", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "TelephonyProviderTests", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + } + ] +} + diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java index a17a19c74e08..9bc534c2877a 100644 --- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java +++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java @@ -27,10 +27,10 @@ import android.os.RemoteException; import android.os.UserHandle; import android.permission.IPermissionManager; import android.provider.Settings; -import android.util.Log; import android.telephony.TelephonyManager; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Log; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; @@ -141,8 +141,8 @@ public final class CarrierAppUtils { ContentResolver contentResolver, int userId, ArraySet<String> systemCarrierAppsDisabledUntilUsed, ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed) { - List<ApplicationInfo> candidates = getDefaultCarrierAppCandidatesHelper(packageManager, - userId, systemCarrierAppsDisabledUntilUsed); + List<ApplicationInfo> candidates = getDefaultNotUpdatedCarrierAppCandidatesHelper( + packageManager, userId, systemCarrierAppsDisabledUntilUsed); if (candidates == null || candidates.isEmpty()) { return; } @@ -178,15 +178,16 @@ public final class CarrierAppUtils { } } + int enabledSetting = packageManager.getApplicationEnabledSetting(packageName, + userId); if (hasPrivileges) { // Only update enabled state for the app on /system. Once it has been // updated we shouldn't touch it. - if (!ai.isUpdatedSystemApp() - && (ai.enabledSetting + if (enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT - || ai.enabledSetting + || enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED - || (ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0)) { + || (ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { Log.i(TAG, "Update state(" + packageName + "): ENABLED for user " + userId); packageManager.setSystemAppInstallState( @@ -204,9 +205,12 @@ public final class CarrierAppUtils { // Also enable any associated apps for this carrier app. if (associatedAppList != null) { for (ApplicationInfo associatedApp : associatedAppList) { - if (associatedApp.enabledSetting + int associatedAppEnabledSetting = + packageManager.getApplicationEnabledSetting( + associatedApp.packageName, userId); + if (associatedAppEnabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT - || associatedApp.enabledSetting + || associatedAppEnabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED || (associatedApp.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { @@ -231,8 +235,7 @@ public final class CarrierAppUtils { } else { // No carrier privileges // Only update enabled state for the app on /system. Once it has been // updated we shouldn't touch it. - if (!ai.isUpdatedSystemApp() - && ai.enabledSetting + if (enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT && (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0) { Log.i(TAG, "Update state(" + packageName @@ -249,7 +252,10 @@ public final class CarrierAppUtils { if (!hasRunOnce) { if (associatedAppList != null) { for (ApplicationInfo associatedApp : associatedAppList) { - if (associatedApp.enabledSetting + int associatedAppEnabledSetting = + packageManager.getApplicationEnabledSetting( + associatedApp.packageName, userId); + if (associatedAppEnabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT && (associatedApp.flags & ApplicationInfo.FLAG_INSTALLED) != 0) { @@ -360,6 +366,31 @@ public final class CarrierAppUtils { return apps; } + private static List<ApplicationInfo> getDefaultNotUpdatedCarrierAppCandidatesHelper( + IPackageManager packageManager, + int userId, + ArraySet<String> systemCarrierAppsDisabledUntilUsed) { + if (systemCarrierAppsDisabledUntilUsed == null) { + return null; + } + + int size = systemCarrierAppsDisabledUntilUsed.size(); + if (size == 0) { + return null; + } + + List<ApplicationInfo> apps = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + String packageName = systemCarrierAppsDisabledUntilUsed.valueAt(i); + ApplicationInfo ai = + getApplicationInfoIfNotUpdatedSystemApp(packageManager, userId, packageName); + if (ai != null) { + apps.add(ai); + } + } + return apps; + } + private static Map<String, List<ApplicationInfo>> getDefaultCarrierAssociatedAppsHelper( IPackageManager packageManager, int userId, @@ -372,11 +403,11 @@ public final class CarrierAppUtils { systemCarrierAssociatedAppsDisabledUntilUsed.valueAt(i); for (int j = 0; j < associatedAppPackages.size(); j++) { ApplicationInfo ai = - getApplicationInfoIfSystemApp( + getApplicationInfoIfNotUpdatedSystemApp( packageManager, userId, associatedAppPackages.get(j)); // Only update enabled state for the app on /system. Once it has been updated we // shouldn't touch it. - if (ai != null && !ai.isUpdatedSystemApp()) { + if (ai != null) { List<ApplicationInfo> appList = associatedApps.get(carrierAppPackage); if (appList == null) { appList = new ArrayList<>(); @@ -390,6 +421,26 @@ public final class CarrierAppUtils { } @Nullable + private static ApplicationInfo getApplicationInfoIfNotUpdatedSystemApp( + IPackageManager packageManager, + int userId, + String packageName) { + try { + ApplicationInfo ai = packageManager.getApplicationInfo(packageName, + PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS + | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS + | PackageManager.MATCH_SYSTEM_ONLY + | PackageManager.MATCH_FACTORY_ONLY, userId); + if (ai != null) { + return ai; + } + } catch (RemoteException e) { + Log.w(TAG, "Could not reach PackageManager", e); + } + return null; + } + + @Nullable private static ApplicationInfo getApplicationInfoIfSystemApp( IPackageManager packageManager, int userId, @@ -397,8 +448,9 @@ public final class CarrierAppUtils { try { ApplicationInfo ai = packageManager.getApplicationInfo(packageName, PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS - | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS, userId); - if (ai != null && ai.isSystemApp()) { + | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS + | PackageManager.MATCH_SYSTEM_ONLY, userId); + if (ai != null) { return ai; } } catch (RemoteException e) { diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java index f6ce0dc827d8..b8b203def6b8 100644 --- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java @@ -592,7 +592,7 @@ public final class TelephonyPermissions { private static boolean checkCarrierPrivilegeForAnySubId(Context context, int uid) { SubscriptionManager sm = (SubscriptionManager) context.getSystemService( Context.TELEPHONY_SUBSCRIPTION_SERVICE); - int[] activeSubIds = sm.getActiveSubscriptionIdList(/* visibleOnly */ false); + int[] activeSubIds = sm.getActiveAndHiddenSubscriptionIdList(); for (int activeSubId : activeSubIds) { if (getCarrierPrivilegeStatus(context, activeSubId, uid) == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java index 2abcc76fdccc..a7ad884ca107 100644 --- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java +++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java @@ -28,6 +28,8 @@ import android.os.RemoteException; import android.os.SystemProperties; import java.io.PrintWriter; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import java.util.function.Supplier; /** @@ -137,4 +139,12 @@ public final class TelephonyUtils { } return ret; } + + /** Wait for latch to trigger */ + public static void waitUntilReady(CountDownLatch latch, long timeoutMs) { + try { + latch.await(timeoutMs, TimeUnit.MILLISECONDS); + } catch (InterruptedException ignored) { + } + } } diff --git a/telephony/framework-telephony-jarjar-rules.txt b/telephony/framework-telephony-jarjar-rules.txt new file mode 100644 index 000000000000..7cab806298a4 --- /dev/null +++ b/telephony/framework-telephony-jarjar-rules.txt @@ -0,0 +1,4 @@ +rule android.telephony.Annotation* android.telephony.framework.Annotation@1 +rule com.android.i18n.phonenumbers.** com.android.telephony.framework.phonenumbers.@1 +#TODO: add jarjar rules for statically linked util classes + diff --git a/telephony/java/android/telephony/BarringInfo.aidl b/telephony/java/android/telephony/BarringInfo.aidl new file mode 100644 index 000000000000..50ddf6b31919 --- /dev/null +++ b/telephony/java/android/telephony/BarringInfo.aidl @@ -0,0 +1,20 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** @hide */ +package android.telephony; + +parcelable BarringInfo; diff --git a/telephony/java/android/telephony/BarringInfo.java b/telephony/java/android/telephony/BarringInfo.java new file mode 100644 index 000000000000..5419c3c3d5c4 --- /dev/null +++ b/telephony/java/android/telephony/BarringInfo.java @@ -0,0 +1,398 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.annotation.TestApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.SparseArray; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.List; +import java.util.Objects; + +/** + * Provides the barring configuration for a particular service type. + * + * Provides indication about the barring of a particular service for use. Certain barring types + * are only valid for certain technology families. Any service that does not have a barring + * configuration is unbarred by default. + */ +public final class BarringInfo implements Parcelable { + + /** + * Barring Service Type + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "BARRING_SERVICE_TYPE_", value = { + BARRING_SERVICE_TYPE_CS_SERVICE, + BARRING_SERVICE_TYPE_PS_SERVICE, + BARRING_SERVICE_TYPE_CS_VOICE, + BARRING_SERVICE_TYPE_MO_SIGNALLING, + BARRING_SERVICE_TYPE_MO_DATA, + BARRING_SERVICE_TYPE_CS_FALLBACK, + BARRING_SERVICE_TYPE_MMTEL_VOICE, + BARRING_SERVICE_TYPE_MMTEL_VIDEO, + BARRING_SERVICE_TYPE_EMERGENCY, + BARRING_SERVICE_TYPE_SMS}) + public @interface BarringServiceType {} + + /* Applicabe to UTRAN */ + /** Barring indicator for circuit-switched service; applicable to UTRAN */ + public static final int BARRING_SERVICE_TYPE_CS_SERVICE = + android.hardware.radio.V1_5.BarringServiceType.CS_SERVICE; + /** Barring indicator for packet-switched service; applicable to UTRAN */ + public static final int BARRING_SERVICE_TYPE_PS_SERVICE = + android.hardware.radio.V1_5.BarringServiceType.PS_SERVICE; + /** Barring indicator for circuit-switched voice service; applicable to UTRAN */ + public static final int BARRING_SERVICE_TYPE_CS_VOICE = + android.hardware.radio.V1_5.BarringServiceType.CS_VOICE; + + /* Applicable to EUTRAN, NGRAN */ + /** Barring indicator for mobile-originated signalling; applicable to EUTRAN and NGRAN */ + public static final int BARRING_SERVICE_TYPE_MO_SIGNALLING = + android.hardware.radio.V1_5.BarringServiceType.MO_SIGNALLING; + /** Barring indicator for mobile-originated data traffic; applicable to EUTRAN and NGRAN */ + public static final int BARRING_SERVICE_TYPE_MO_DATA = + android.hardware.radio.V1_5.BarringServiceType.MO_DATA; + /** Barring indicator for circuit-switched fallback for voice; applicable to EUTRAN and NGRAN */ + public static final int BARRING_SERVICE_TYPE_CS_FALLBACK = + android.hardware.radio.V1_5.BarringServiceType.CS_FALLBACK; + /** Barring indicator for MMTEL (IMS) voice; applicable to EUTRAN and NGRAN */ + public static final int BARRING_SERVICE_TYPE_MMTEL_VOICE = + android.hardware.radio.V1_5.BarringServiceType.MMTEL_VOICE; + /** Barring indicator for MMTEL (IMS) video; applicable to EUTRAN and NGRAN */ + public static final int BARRING_SERVICE_TYPE_MMTEL_VIDEO = + android.hardware.radio.V1_5.BarringServiceType.MMTEL_VIDEO; + + /* Applicable to UTRAN, EUTRAN, NGRAN */ + /** Barring indicator for emergency services; applicable to UTRAN, EUTRAN, and NGRAN */ + public static final int BARRING_SERVICE_TYPE_EMERGENCY = + android.hardware.radio.V1_5.BarringServiceType.EMERGENCY; + /** Barring indicator for SMS sending; applicable to UTRAN, EUTRAN, and NGRAN */ + public static final int BARRING_SERVICE_TYPE_SMS = + android.hardware.radio.V1_5.BarringServiceType.SMS; + + //TODO: add barring constants for Operator-Specific barring codes + + /** Describe the current barring configuration of a cell */ + public static final class BarringServiceInfo implements Parcelable { + /** + * Barring Type + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "BARRING_TYPE_", value = + {BARRING_TYPE_NONE, + BARRING_TYPE_UNCONDITIONAL, + BARRING_TYPE_CONDITIONAL, + BARRING_TYPE_UNKNOWN}) + public @interface BarringType {} + + /** Barring is inactive */ + public static final int BARRING_TYPE_NONE = android.hardware.radio.V1_5.BarringType.NONE; + /** The service is barred */ + public static final int BARRING_TYPE_UNCONDITIONAL = + android.hardware.radio.V1_5.BarringType.UNCONDITIONAL; + /** The service may be barred based on additional factors */ + public static final int BARRING_TYPE_CONDITIONAL = + android.hardware.radio.V1_5.BarringType.CONDITIONAL; + + /** If a modem does not report barring info, then the barring type will be UNKNOWN */ + public static final int BARRING_TYPE_UNKNOWN = -1; + + private final @BarringType int mBarringType; + + private final boolean mIsConditionallyBarred; + private final int mConditionalBarringFactor; + private final int mConditionalBarringTimeSeconds; + + /** @hide */ + public BarringServiceInfo(@BarringType int type) { + this(type, false, 0, 0); + } + + /** @hide */ + @TestApi + public BarringServiceInfo(@BarringType int barringType, boolean isConditionallyBarred, + int conditionalBarringFactor, int conditionalBarringTimeSeconds) { + mBarringType = barringType; + mIsConditionallyBarred = isConditionallyBarred; + mConditionalBarringFactor = conditionalBarringFactor; + mConditionalBarringTimeSeconds = conditionalBarringTimeSeconds; + } + + public @BarringType int getBarringType() { + return mBarringType; + } + + /** + * @return true if the conditional barring parameters have resulted in the service being + * barred; false if the service has either not been evaluated for conditional + * barring or has been evaluated and isn't barred. + */ + public boolean isConditionallyBarred() { + return mIsConditionallyBarred; + } + + /** + * @return the conditional barring factor as a percentage 0-100, which is the probability of + * a random device being barred for the service type. + */ + public int getConditionalBarringFactor() { + return mConditionalBarringFactor; + } + + /** + * @return the conditional barring time seconds, which is the interval between successive + * evaluations for conditional barring based on the barring factor. + */ + @SuppressLint("MethodNameUnits") + public int getConditionalBarringTimeSeconds() { + return mConditionalBarringTimeSeconds; + } + + /** + * Return whether a service is currently barred based on the BarringInfo + * + * @return true if the service is currently being barred, otherwise false + */ + public boolean isBarred() { + return mBarringType == BarringServiceInfo.BARRING_TYPE_UNCONDITIONAL + || (mBarringType == BarringServiceInfo.BARRING_TYPE_CONDITIONAL + && mIsConditionallyBarred); + } + + @Override + public int hashCode() { + return Objects.hash(mBarringType, mIsConditionallyBarred, + mConditionalBarringFactor, mConditionalBarringTimeSeconds); + } + + @Override + public boolean equals(Object rhs) { + if (!(rhs instanceof BarringServiceInfo)) return false; + + BarringServiceInfo other = (BarringServiceInfo) rhs; + return mBarringType == other.mBarringType + && mIsConditionallyBarred == other.mIsConditionallyBarred + && mConditionalBarringFactor == other.mConditionalBarringFactor + && mConditionalBarringTimeSeconds == other.mConditionalBarringTimeSeconds; + } + + /** @hide */ + public BarringServiceInfo(Parcel p) { + mBarringType = p.readInt(); + mIsConditionallyBarred = p.readBoolean(); + mConditionalBarringFactor = p.readInt(); + mConditionalBarringTimeSeconds = p.readInt(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mBarringType); + dest.writeBoolean(mIsConditionallyBarred); + dest.writeInt(mConditionalBarringFactor); + dest.writeInt(mConditionalBarringTimeSeconds); + } + + /* @inheritDoc */ + public static final @NonNull Parcelable.Creator<BarringServiceInfo> CREATOR = + new Parcelable.Creator<BarringServiceInfo>() { + @Override + public BarringServiceInfo createFromParcel(Parcel source) { + return new BarringServiceInfo(source); + } + + @Override + public BarringServiceInfo[] newArray(int size) { + return new BarringServiceInfo[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + } + + private CellIdentity mCellIdentity; + + // A SparseArray potentially mapping each BarringService type to a BarringServiceInfo config + // that describes the current barring status of that particular service. + private SparseArray<BarringServiceInfo> mBarringServiceInfos; + + /** @hide */ + @TestApi + @SystemApi + public BarringInfo() { + mBarringServiceInfos = new SparseArray<>(); + } + + /** + * Constructor for new BarringInfo instances. + * + * @hide + */ + @TestApi + public BarringInfo(@Nullable CellIdentity barringCellId, + @NonNull SparseArray<BarringServiceInfo> barringServiceInfos) { + mCellIdentity = barringCellId; + mBarringServiceInfos = barringServiceInfos; + } + + /** @hide */ + public static BarringInfo create( + @NonNull android.hardware.radio.V1_5.CellIdentity halBarringCellId, + @NonNull List<android.hardware.radio.V1_5.BarringInfo> halBarringInfos) { + CellIdentity ci = CellIdentity.create(halBarringCellId); + SparseArray<BarringServiceInfo> serviceInfos = new SparseArray<>(); + + for (android.hardware.radio.V1_5.BarringInfo halBarringInfo : halBarringInfos) { + if (halBarringInfo.type == android.hardware.radio.V1_5.BarringType.CONDITIONAL) { + if (halBarringInfo.typeSpecificInfo.getDiscriminator() + != android.hardware.radio.V1_5.BarringTypeSpecificInfo + .hidl_discriminator.conditionalBarringInfo) { + // this is an error case where the barring info is conditional but the + // conditional barring fields weren't included + continue; + } + android.hardware.radio.V1_5.ConditionalBarringInfo conditionalInfo = + halBarringInfo.typeSpecificInfo.conditionalBarringInfo(); + serviceInfos.put( + halBarringInfo.service, new BarringServiceInfo( + halBarringInfo.type, // will always be CONDITIONAL here + conditionalInfo.isBarred, + conditionalInfo.barringFactor, + conditionalInfo.barringTimeSeconds)); + } else { + // Barring type is either NONE or UNCONDITIONAL + serviceInfos.put( + halBarringInfo.service, new BarringServiceInfo(halBarringInfo.type, + false, 0, 0)); + } + } + return new BarringInfo(ci, serviceInfos); + } + + /** + * Return whether a service is currently barred based on the BarringInfo + * + * @param service the service to be checked. + * @return true if the service is currently being barred, otherwise false + */ + public boolean isServiceBarred(@BarringServiceType int service) { + BarringServiceInfo bsi = mBarringServiceInfos.get(service); + return bsi != null && (bsi.isBarred()); + } + + /** + * Get the BarringServiceInfo for a specified service. + * + * @return a BarringServiceInfo struct describing the current barring status for a service + */ + public @NonNull BarringServiceInfo getBarringServiceInfo(@BarringServiceType int service) { + BarringServiceInfo bsi = mBarringServiceInfos.get(service); + // If barring is reported but not for a particular service, then we report the barring + // type as UNKNOWN; if the modem reports barring info but doesn't report for a particular + // service then we can safely assume that the service isn't barred (for instance because + // that particular service isn't applicable to the current RAN). + return (bsi != null) ? bsi : new BarringServiceInfo( + mBarringServiceInfos.size() > 0 ? BarringServiceInfo.BARRING_TYPE_NONE : + BarringServiceInfo.BARRING_TYPE_UNKNOWN); + } + + /** @hide */ + @SystemApi + public @NonNull BarringInfo createLocationInfoSanitizedCopy() { + return new BarringInfo(mCellIdentity.sanitizeLocationInfo(), mBarringServiceInfos); + } + + /** @hide */ + public BarringInfo(Parcel p) { + mCellIdentity = p.readParcelable(CellIdentity.class.getClassLoader()); + mBarringServiceInfos = p.readSparseArray(BarringServiceInfo.class.getClassLoader()); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeParcelable(mCellIdentity, flags); + dest.writeSparseArray(mBarringServiceInfos); + } + + public static final @NonNull Parcelable.Creator<BarringInfo> CREATOR = + new Parcelable.Creator<BarringInfo>() { + @Override + public BarringInfo createFromParcel(Parcel source) { + return new BarringInfo(source); + } + + @Override + public BarringInfo[] newArray(int size) { + return new BarringInfo[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public int hashCode() { + int hash = mCellIdentity != null ? mCellIdentity.hashCode() : 7; + for (int i = 0; i < mBarringServiceInfos.size(); i++) { + hash = hash + 15 * mBarringServiceInfos.keyAt(i); + hash = hash + 31 * mBarringServiceInfos.valueAt(i).hashCode(); + } + return hash; + } + + @Override + public boolean equals(Object rhs) { + if (!(rhs instanceof BarringInfo)) return false; + + BarringInfo bi = (BarringInfo) rhs; + + if (hashCode() != bi.hashCode()) return false; + + if (mBarringServiceInfos.size() != bi.mBarringServiceInfos.size()) return false; + + for (int i = 0; i < mBarringServiceInfos.size(); i++) { + if (mBarringServiceInfos.keyAt(i) != bi.mBarringServiceInfos.keyAt(i)) return false; + if (!Objects.equals(mBarringServiceInfos.valueAt(i), + bi.mBarringServiceInfos.valueAt(i))) { + return false; + } + } + return true; + } + + @Override + public String toString() { + return "BarringInfo {mCellIdentity=" + mCellIdentity + + ", mBarringServiceInfos=" + mBarringServiceInfos + "}"; + } +} diff --git a/telephony/java/android/telephony/CallQuality.java b/telephony/java/android/telephony/CallQuality.java index e01deb2802ff..1e1cdba70ad0 100644 --- a/telephony/java/android/telephony/CallQuality.java +++ b/telephony/java/android/telephony/CallQuality.java @@ -80,6 +80,9 @@ public final class CallQuality implements Parcelable { private int mMaxRelativeJitter; private int mAverageRoundTripTime; private int mCodecType; + private boolean mRtpInactivityDetected; + private boolean mRxSilenceDetected; + private boolean mTxSilenceDetected; /** @hide **/ public CallQuality(Parcel in) { @@ -94,6 +97,9 @@ public final class CallQuality implements Parcelable { mMaxRelativeJitter = in.readInt(); mAverageRoundTripTime = in.readInt(); mCodecType = in.readInt(); + mRtpInactivityDetected = in.readBoolean(); + mRxSilenceDetected = in.readBoolean(); + mTxSilenceDetected = in.readBoolean(); } /** @hide **/ @@ -109,7 +115,7 @@ public final class CallQuality implements Parcelable { * @param numRtpPacketsReceived RTP packets received from network * @param numRtpPacketsTransmittedLost RTP packets which were lost in network and never * transmitted - * @param numRtpPacketsNotReceived RTP packets which were lost in network and never recieved + * @param numRtpPacketsNotReceived RTP packets which were lost in network and never received * @param averageRelativeJitter average relative jitter in milliseconds * @param maxRelativeJitter maximum relative jitter in milliseconds * @param averageRoundTripTime average round trip delay in milliseconds @@ -127,6 +133,48 @@ public final class CallQuality implements Parcelable { int maxRelativeJitter, int averageRoundTripTime, int codecType) { + this(downlinkCallQualityLevel, uplinkCallQualityLevel, callDuration, + numRtpPacketsTransmitted, numRtpPacketsReceived, numRtpPacketsTransmittedLost, + numRtpPacketsNotReceived, averageRelativeJitter, maxRelativeJitter, + averageRoundTripTime, codecType, false, false, false); + } + + /** + * Constructor. + * + * @param callQualityLevel the call quality level (see #CallQualityLevel) + * @param callDuration the call duration in milliseconds + * @param numRtpPacketsTransmitted RTP packets sent to network + * @param numRtpPacketsReceived RTP packets received from network + * @param numRtpPacketsTransmittedLost RTP packets which were lost in network and never + * transmitted + * @param numRtpPacketsNotReceived RTP packets which were lost in network and never received + * @param averageRelativeJitter average relative jitter in milliseconds + * @param maxRelativeJitter maximum relative jitter in milliseconds + * @param averageRoundTripTime average round trip delay in milliseconds + * @param codecType the codec type + * @param rtpInactivityDetected True if no incoming RTP is received for a continuous duration of + * 4 seconds + * @param rxSilenceDetected True if only silence RTP packets are received for 20 seconds + * immediately after call is connected + * @param txSilenceDetected True if only silence RTP packets are sent for 20 seconds immediately + * after call is connected + */ + public CallQuality( + @CallQualityLevel int downlinkCallQualityLevel, + @CallQualityLevel int uplinkCallQualityLevel, + int callDuration, + int numRtpPacketsTransmitted, + int numRtpPacketsReceived, + int numRtpPacketsTransmittedLost, + int numRtpPacketsNotReceived, + int averageRelativeJitter, + int maxRelativeJitter, + int averageRoundTripTime, + int codecType, + boolean rtpInactivityDetected, + boolean rxSilenceDetected, + boolean txSilenceDetected) { this.mDownlinkCallQualityLevel = downlinkCallQualityLevel; this.mUplinkCallQualityLevel = uplinkCallQualityLevel; this.mCallDuration = callDuration; @@ -138,6 +186,9 @@ public final class CallQuality implements Parcelable { this.mMaxRelativeJitter = maxRelativeJitter; this.mAverageRoundTripTime = averageRoundTripTime; this.mCodecType = codecType; + this.mRtpInactivityDetected = rtpInactivityDetected; + this.mRxSilenceDetected = rxSilenceDetected; + this.mTxSilenceDetected = txSilenceDetected; } // getters @@ -226,6 +277,29 @@ public final class CallQuality implements Parcelable { } /** + * Returns true if no rtp packets are received continuously for the last 4 seconds + */ + public boolean isRtpInactivityDetected() { + return mRtpInactivityDetected; + } + + /** + * Returns true if only silence rtp packets are received for a duration of 20 seconds starting + * at call setup + */ + public boolean isIncomingSilenceDetected() { + return mRxSilenceDetected; + } + + /** + * Returns true if only silence rtp packets are sent for a duration of 20 seconds starting at + * call setup + */ + public boolean isOutgoingSilenceDetected() { + return mTxSilenceDetected; + } + + /** * Returns the codec type. This value corresponds to the AUDIO_QUALITY_* constants in * {@link ImsStreamMediaProfile}. * @@ -270,6 +344,9 @@ public final class CallQuality implements Parcelable { + " maxRelativeJitter=" + mMaxRelativeJitter + " averageRoundTripTime=" + mAverageRoundTripTime + " codecType=" + mCodecType + + " rtpInactivityDetected=" + mRtpInactivityDetected + + " txSilenceDetected=" + mRxSilenceDetected + + " rxSilenceDetected=" + mTxSilenceDetected + "}"; } @@ -286,7 +363,10 @@ public final class CallQuality implements Parcelable { mAverageRelativeJitter, mMaxRelativeJitter, mAverageRoundTripTime, - mCodecType); + mCodecType, + mRtpInactivityDetected, + mRxSilenceDetected, + mTxSilenceDetected); } @Override @@ -311,7 +391,10 @@ public final class CallQuality implements Parcelable { && mAverageRelativeJitter == s.mAverageRelativeJitter && mMaxRelativeJitter == s.mMaxRelativeJitter && mAverageRoundTripTime == s.mAverageRoundTripTime - && mCodecType == s.mCodecType); + && mCodecType == s.mCodecType + && mRtpInactivityDetected == s.mRtpInactivityDetected + && mRxSilenceDetected == s.mRxSilenceDetected + && mTxSilenceDetected == s.mTxSilenceDetected); } /** @@ -336,6 +419,9 @@ public final class CallQuality implements Parcelable { dest.writeInt(mMaxRelativeJitter); dest.writeInt(mAverageRoundTripTime); dest.writeInt(mCodecType); + dest.writeBoolean(mRtpInactivityDetected); + dest.writeBoolean(mRxSilenceDetected); + dest.writeBoolean(mTxSilenceDetected); } public static final @android.annotation.NonNull Parcelable.Creator<CallQuality> CREATOR = new Parcelable.Creator() { diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index c641c9cc9096..5a7c3b373b60 100755 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -16,8 +16,6 @@ package android.telephony; -import com.android.telephony.Rlog; - import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; @@ -36,6 +34,7 @@ import android.telecom.TelecomManager; import android.telephony.ims.ImsReasonInfo; import com.android.internal.telephony.ICarrierConfigLoader; +import com.android.telephony.Rlog; /** * Provides access to telephony configuration values that are carrier-specific. @@ -300,7 +299,6 @@ public class CarrierConfigManager { /** * A string array containing numbers that shouldn't be included in the call log. - * @hide */ public static final String KEY_UNLOGGABLE_NUMBERS_STRING_ARRAY = "unloggable_numbers_string_array"; @@ -313,12 +311,11 @@ public class CarrierConfigManager { KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL = "hide_carrier_network_settings_bool"; /** - * Do only allow auto selection in Advanced Network Settings when in home network. + * Only allow auto selection in Advanced Network Settings when in home network. * Manual selection is allowed when in roaming network. - * @hide */ - public static final String - KEY_ONLY_AUTO_SELECT_IN_HOME_NETWORK_BOOL = "only_auto_select_in_home_network"; + public static final String KEY_ONLY_AUTO_SELECT_IN_HOME_NETWORK_BOOL = + "only_auto_select_in_home_network"; /** * Control whether users receive a simplified network settings UI and improved network @@ -582,9 +579,6 @@ public class CarrierConfigManager { * registration state to change. That is, turning on or off mobile data will not cause VT to be * enabled or disabled. * When {@code false}, disabling mobile data will cause VT to be de-registered. - * <p> - * See also {@link #KEY_VILTE_DATA_IS_METERED_BOOL}. - * @hide */ public static final String KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS = "ignore_data_enabled_changed_for_video_calls"; @@ -648,7 +642,6 @@ public class CarrierConfigManager { /** * Default WFC_IMS_enabled: true VoWiFi by default is on * false VoWiFi by default is off - * @hide */ public static final String KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL = "carrier_default_wfc_ims_enabled_bool"; @@ -672,6 +665,12 @@ public class CarrierConfigManager { "carrier_promote_wfc_on_call_fail_bool"; /** + * Flag specifying whether provisioning is required for RCS. + */ + public static final String KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL = + "carrier_rcs_provisioning_required_bool"; + + /** * Flag specifying whether provisioning is required for VoLTE, Video Telephony, and WiFi * Calling. */ @@ -714,9 +713,7 @@ public class CarrierConfigManager { * * As of now, Verizon is the only carrier enforcing this dependency in their * WFC awareness and activation requirements. - * - * @hide - * */ + */ public static final String KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL = "carrier_volte_override_wfc_provisioning_bool"; @@ -1077,7 +1074,6 @@ public class CarrierConfigManager { * * When {@code false}, the old behavior is used, where the toggle in accessibility settings is * used to set the IMS stack's RTT enabled state. - * @hide */ public static final String KEY_IGNORE_RTT_MODE_SETTING_BOOL = "ignore_rtt_mode_setting_bool"; @@ -1118,7 +1114,6 @@ public class CarrierConfigManager { * Determines whether the IMS conference merge process supports and returns its participants * data. When {@code true}, on merge complete, conference call would have a list of its * participants returned in XML format, {@code false otherwise}. - * @hide */ public static final String KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_BOOL = "support_ims_conference_event_package_bool"; @@ -1191,20 +1186,18 @@ public class CarrierConfigManager { public static final String KEY_ENABLE_APPS_STRING_ARRAY = "enable_apps_string_array"; /** - * Determine whether user can switch Wi-Fi preferred or Cellular preferred in calling preference. + * Determine whether user can switch Wi-Fi preferred or Cellular preferred + * in calling preference. * Some operators support Wi-Fi Calling only, not VoLTE. * They don't need "Cellular preferred" option. - * In this case, set uneditalbe attribute for preferred preference. - * @hide + * In this case, set uneditable attribute for preferred preference. */ public static final String KEY_EDITABLE_WFC_MODE_BOOL = "editable_wfc_mode_bool"; - /** - * Flag to indicate if Wi-Fi needs to be disabled in ECBM - * @hide - **/ - public static final String - KEY_CONFIG_WIFI_DISABLE_IN_ECBM = "config_wifi_disable_in_ecbm"; + /** + * Flag to indicate if Wi-Fi needs to be disabled in ECBM. + */ + public static final String KEY_CONFIG_WIFI_DISABLE_IN_ECBM = "config_wifi_disable_in_ecbm"; /** * List operator-specific error codes and indices of corresponding error strings in @@ -1268,9 +1261,8 @@ public class CarrierConfigManager { public static final String KEY_WFC_SPN_USE_ROOT_LOCALE = "wfc_spn_use_root_locale"; /** - * The Component Name of the activity that can setup the emergency addrees for WiFi Calling + * The Component Name of the activity that can setup the emergency address for WiFi Calling * as per carrier requirement. - * @hide */ public static final String KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING = "wfc_emergency_address_carrier_app_string"; @@ -1434,22 +1426,19 @@ public class CarrierConfigManager { public static final String KEY_ALLOW_ADDING_APNS_BOOL = "allow_adding_apns_bool"; /** - * APN types that user is not allowed to modify - * @hide + * APN types that user is not allowed to modify. */ public static final String KEY_READ_ONLY_APN_TYPES_STRING_ARRAY = "read_only_apn_types_string_array"; /** - * APN fields that user is not allowed to modify - * @hide + * APN fields that user is not allowed to modify. */ public static final String KEY_READ_ONLY_APN_FIELDS_STRING_ARRAY = "read_only_apn_fields_string_array"; /** * Default value of APN types field if not specified by user when adding/modifying an APN. - * @hide */ public static final String KEY_APN_SETTINGS_DEFAULT_APN_TYPES_STRING_ARRAY = "apn_settings_default_apn_types_string_array"; @@ -1480,29 +1469,25 @@ public class CarrierConfigManager { "hide_digits_helper_text_on_stk_input_screen_bool"; /** - * Boolean indicating if show data RAT icon on status bar even when data is disabled - * @hide + * Boolean indicating if show data RAT icon on status bar even when data is disabled. */ public static final String KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL = "always_show_data_rat_icon_bool"; /** - * Boolean indicating if default data account should show LTE or 4G icon - * @hide + * Boolean indicating if default data account should show LTE or 4G icon. */ public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL = "show_4g_for_lte_data_icon_bool"; /** * Boolean indicating if default data account should show 4G icon when in 3G. - * @hide */ public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL = "show_4g_for_3g_data_icon_bool"; /** - * Boolean indicating if lte+ icon should be shown if available - * @hide + * Boolean indicating if LTE+ icon should be shown if available. */ public static final String KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL = "hide_lte_plus_data_icon_bool"; @@ -1517,10 +1502,8 @@ public class CarrierConfigManager { "operator_name_filter_pattern_string"; /** - * The string is used to compare with operator name. If it matches the pattern then show - * specific data icon. - * - * @hide + * The string is used to compare with operator name. + * If it matches the pattern then show specific data icon. */ public static final String KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING = "show_carrier_data_icon_pattern_string"; @@ -1533,33 +1516,28 @@ public class CarrierConfigManager { "show_precise_failed_cause_bool"; /** - * Boolean to decide whether lte is enabled. - * @hide + * Boolean to decide whether LTE is enabled. */ public static final String KEY_LTE_ENABLED_BOOL = "lte_enabled_bool"; /** * Boolean to decide whether TD-SCDMA is supported. - * @hide */ public static final String KEY_SUPPORT_TDSCDMA_BOOL = "support_tdscdma_bool"; /** * A list of mcc/mnc that support TD-SCDMA for device when connect to the roaming network. - * @hide */ public static final String KEY_SUPPORT_TDSCDMA_ROAMING_NETWORKS_STRING_ARRAY = "support_tdscdma_roaming_networks_string_array"; /** * Boolean to decide whether world mode is enabled. - * @hide */ public static final String KEY_WORLD_MODE_ENABLED_BOOL = "world_mode_enabled_bool"; /** * Flatten {@link android.content.ComponentName} of the carrier's settings activity. - * @hide */ public static final String KEY_CARRIER_SETTINGS_ACTIVITY_COMPONENT_NAME_STRING = "carrier_settings_activity_component_name_string"; @@ -1613,25 +1591,23 @@ public class CarrierConfigManager { /** * Defines carrier-specific actions which act upon * com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED, used for customization of the - * default carrier app + * default carrier app. * Format: "CARRIER_ACTION_IDX, ..." * Where {@code CARRIER_ACTION_IDX} is an integer defined in - * {@link com.android.carrierdefaultapp.CarrierActionUtils CarrierActionUtils} + * com.android.carrierdefaultapp.CarrierActionUtils * Example: - * {@link com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_DISABLE_METERED_APNS - * disable_metered_apns} - * @hide + * com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_DISABLE_METERED_APNS + * disables metered APNs */ - @UnsupportedAppUsage + @SuppressLint("IntentName") public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY = "carrier_default_actions_on_redirection_string_array"; /** - * Defines carrier-specific actions which act upon - * com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED + * Defines carrier-specific actions which act upon CARRIER_SIGNAL_REQUEST_NETWORK_FAILED * and configured signal args: - * {@link TelephonyManager#EXTRA_APN_TYPE apnType}, - * {@link TelephonyManager#EXTRA_ERROR_CODE errorCode} + * android.telephony.TelephonyManager#EXTRA_APN_TYPE, + * android.telephony.TelephonyManager#EXTRA_ERROR_CODE * used for customization of the default carrier app * Format: * { @@ -1639,42 +1615,41 @@ public class CarrierConfigManager { * "APN_1, ERROR_CODE_2 : CARRIER_ACTION_IDX_1 " * } * Where {@code APN_1} is a string defined in - * {@link com.android.internal.telephony.PhoneConstants PhoneConstants} + * com.android.internal.telephony.PhoneConstants * Example: "default" * - * {@code ERROR_CODE_1} is an integer defined in - * {@link DataFailCause DcFailure} + * {@code ERROR_CODE_1} is an integer defined in android.telephony.DataFailCause * Example: - * {@link DataFailCause#MISSING_UNKNOWN_APN} + * android.telephony.DataFailCause#MISSING_UNKNOWN_APN * * {@code CARRIER_ACTION_IDX_1} is an integer defined in - * {@link com.android.carrierdefaultapp.CarrierActionUtils CarrierActionUtils} + * com.android.carrierdefaultapp.CarrierActionUtils * Example: - * {@link com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_DISABLE_METERED_APNS} - * @hide + * com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_DISABLE_METERED_APNS + * disables metered APNs */ + @SuppressLint("IntentName") public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_DCFAILURE_STRING_ARRAY = "carrier_default_actions_on_dcfailure_string_array"; /** - * Defines carrier-specific actions which act upon - * com.android.internal.telephony.CARRIER_SIGNAL_RESET, used for customization of the - * default carrier app + * Defines carrier-specific actions which act upon CARRIER_SIGNAL_RESET, + * used for customization of the default carrier app. * Format: "CARRIER_ACTION_IDX, ..." * Where {@code CARRIER_ACTION_IDX} is an integer defined in - * {@link com.android.carrierdefaultapp.CarrierActionUtils CarrierActionUtils} + * com.android.carrierdefaultapp.CarrierActionUtils * Example: - * {@link com.android.carrierdefaultapp.CarrierActionUtils - * #CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS clear all notifications on reset} - * @hide + * com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS + * clears all notifications on reset */ + @SuppressLint("IntentName") public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_RESET = "carrier_default_actions_on_reset_string_array"; /** * Defines carrier-specific actions which act upon * com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE, - * used for customization of the default carrier app + * used for customization of the default carrier app. * Format: * { * "true : CARRIER_ACTION_IDX_1", @@ -1682,17 +1657,17 @@ public class CarrierConfigManager { * } * Where {@code true} is a boolean indicates default network available/unavailable * Where {@code CARRIER_ACTION_IDX} is an integer defined in - * {@link com.android.carrierdefaultapp.CarrierActionUtils CarrierActionUtils} + * com.android.carrierdefaultapp.CarrierActionUtils CarrierActionUtils * Example: - * {@link com.android.carrierdefaultapp.CarrierActionUtils - * #CARRIER_ACTION_ENABLE_DEFAULT_URL_HANDLER enable the app as the default URL handler} - * @hide + * com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_ENABLE_DEFAULT_URL_HANDLER + * enables the app as the default URL handler */ + @SuppressLint("IntentName") public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_DEFAULT_NETWORK_AVAILABLE = "carrier_default_actions_on_default_network_available_string_array"; + /** - * Defines a list of acceptable redirection url for default carrier app - * @hides + * Defines a list of acceptable redirection url for default carrier app. */ public static final String KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY = "carrier_default_redirection_url_string_array"; @@ -1820,10 +1795,10 @@ public class CarrierConfigManager { /** * Determines whether to enable enhanced call blocking feature on the device. - * @see SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED - * @see SystemContract#ENHANCED_SETTING_KEY_BLOCK_PRIVATE - * @see SystemContract#ENHANCED_SETTING_KEY_BLOCK_PAYPHONE - * @see SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNKNOWN + * android.provider.BlockedNumberContract.SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED + * android.provider.BlockedNumberContract.SystemContract#ENHANCED_SETTING_KEY_BLOCK_PRIVATE + * android.provider.BlockedNumberContract.SystemContract#ENHANCED_SETTING_KEY_BLOCK_PAYPHONE + * android.provider.BlockedNumberContract.SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNKNOWN * * <p> * 1. For Single SIM(SS) device, it can be customized in both carrier_config_mccmnc.xml @@ -1833,7 +1808,6 @@ public class CarrierConfigManager { * function is used regardless of SIM. * <p> * If {@code true} enable enhanced call blocking feature on the device, {@code false} otherwise. - * @hide */ public static final String KEY_SUPPORT_ENHANCED_CALL_BLOCKING_BOOL = "support_enhanced_call_blocking_bool"; @@ -1944,7 +1918,6 @@ public class CarrierConfigManager { /** * Flag indicating whether the carrier supports call deflection for an incoming IMS call. - * @hide */ public static final String KEY_CARRIER_ALLOW_DEFLECT_IMS_CALL_BOOL = "carrier_allow_deflect_ims_call_bool"; @@ -2009,8 +1982,6 @@ public class CarrierConfigManager { /** * Whether system apps are allowed to use fallback if carrier video call is not available. * Defaults to {@code true}. - * - * @hide */ public static final String KEY_ALLOW_VIDEO_CALLING_FALLBACK_BOOL = "allow_video_calling_fallback_bool"; @@ -2048,9 +2019,8 @@ public class CarrierConfigManager { "enhanced_4g_lte_title_variant_bool"; /** - * The index indicates the carrier specified title string of Enahnce 4G LTE Mode settings. + * The index indicates the carrier specified title string of Enhanced 4G LTE Mode settings. * Default value is 0, which indicates the default title string. - * @hide */ public static final String KEY_ENHANCED_4G_LTE_TITLE_VARIANT_INT = "enhanced_4g_lte_title_variant_int"; @@ -2094,15 +2064,13 @@ public class CarrierConfigManager { * {@link #KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL} is false. If * {@link #KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL} is true, this * configuration is ignored and roaming preference cannot be changed. - * @hide */ public static final String KEY_EDITABLE_WFC_ROAMING_MODE_BOOL = "editable_wfc_roaming_mode_bool"; /** - * Flag specifying wether to show blocking pay phone option in blocked numbers screen. Only show - * the option if payphone call presentation represents in the carrier's region. - * @hide + * Flag specifying whether to show blocking pay phone option in blocked numbers screen. + * Only show the option if payphone call presentation is present in the carrier's region. */ public static final java.lang.String KEY_SHOW_BLOCKING_PAY_PHONE_OPTION_BOOL = "show_blocking_pay_phone_option_bool"; @@ -2112,7 +2080,6 @@ public class CarrierConfigManager { * {@code false} - roaming preference can be selected separately from the home preference. * {@code true} - roaming preference is the same as home preference and * {@link #KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT} is used as the default value. - * @hide */ public static final String KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL = "use_wfc_home_network_mode_in_roaming_network_bool"; @@ -2140,7 +2107,6 @@ public class CarrierConfigManager { * while the device is registered over WFC. Default value is -1, which indicates * that this notification is not pertinent for a particular carrier. We've added a delay * to prevent false positives. - * @hide */ public static final String KEY_EMERGENCY_NOTIFICATION_DELAY_INT = "emergency_notification_delay_int"; @@ -2191,7 +2157,6 @@ public class CarrierConfigManager { /** * Flag specifying whether to show an alert dialog for video call charges. * By default this value is {@code false}. - * @hide */ public static final String KEY_SHOW_VIDEO_CALL_CHARGES_ALERT_DIALOG_BOOL = "show_video_call_charges_alert_dialog_bool"; @@ -2478,10 +2443,9 @@ public class CarrierConfigManager { /** * Identifies if the key is available for WLAN or EPDG or both. The value is a bitmask. * 0 indicates that neither EPDG or WLAN is enabled. - * 1 indicates that key type {@link TelephonyManager#KEY_TYPE_EPDG} is enabled. - * 2 indicates that key type {@link TelephonyManager#KEY_TYPE_WLAN} is enabled. + * 1 indicates that key type TelephonyManager#KEY_TYPE_EPDG is enabled. + * 2 indicates that key type TelephonyManager#KEY_TYPE_WLAN is enabled. * 3 indicates that both are enabled. - * @hide */ public static final String IMSI_KEY_AVAILABILITY_INT = "imsi_key_availability_int"; @@ -2499,7 +2463,6 @@ public class CarrierConfigManager { /** * Flag specifying whether IMS registration state menu is shown in Status Info setting, * default to false. - * @hide */ public static final String KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL = "show_ims_registration_status_bool"; @@ -2555,7 +2518,6 @@ public class CarrierConfigManager { /** * The flag to disable the popup dialog which warns the user of data charges. - * @hide */ public static final String KEY_DISABLE_CHARGE_INDICATION_BOOL = "disable_charge_indication_bool"; @@ -2620,15 +2582,13 @@ public class CarrierConfigManager { /** * Determines whether any carrier has been identified and its specific config has been applied, * default to false. - * @hide */ public static final String KEY_CARRIER_CONFIG_APPLIED_BOOL = "carrier_config_applied_bool"; /** * Determines whether we should show a warning asking the user to check with their carrier - * on pricing when the user enabled data roaming. + * on pricing when the user enabled data roaming, * default to false. - * @hide */ public static final String KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL = "check_pricing_with_carrier_data_roaming_bool"; @@ -2780,10 +2740,10 @@ public class CarrierConfigManager { * Specifies a carrier-defined {@link android.telecom.CallRedirectionService} which Telecom * will bind to for outgoing calls. An empty string indicates that no carrier-defined * {@link android.telecom.CallRedirectionService} is specified. - * @hide */ public static final String KEY_CALL_REDIRECTION_SERVICE_COMPONENT_NAME_STRING = "call_redirection_service_component_name_string"; + /** * Support for the original string display of CDMA MO call. * By default, it is disabled. @@ -2896,8 +2856,8 @@ public class CarrierConfigManager { "call_waiting_service_class_int"; /** - * This configuration allow the system UI to display different 5G icon for different 5G - * scenario. + * This configuration allows the system UI to display different 5G icons for different 5G + * scenarios. * * There are five 5G scenarios: * 1. connected_mmwave: device currently connected to 5G cell as the secondary cell and using @@ -2914,24 +2874,22 @@ public class CarrierConfigManager { * 5G cell as a secondary cell) but the use of 5G is restricted. * * The configured string contains multiple key-value pairs separated by comma. For each pair, - * the key and value is separated by a colon. The key is corresponded to a 5G status above and + * the key and value are separated by a colon. The key corresponds to a 5G status above and * the value is the icon name. Use "None" as the icon name if no icon should be shown in a * specific 5G scenario. If the scenario is "None", config can skip this key and value. * * Icon name options: "5G_Plus", "5G". * * Here is an example: - * UE want to display 5G_Plus icon for scenario#1, and 5G icon for scenario#2; otherwise no + * UE wants to display 5G_Plus icon for scenario#1, and 5G icon for scenario#2; otherwise not * define. * The configuration is: "connected_mmwave:5G_Plus,connected:5G" - * - * @hide */ public static final String KEY_5G_ICON_CONFIGURATION_STRING = "5g_icon_configuration_string"; /** - * Timeout in second for displaying 5G icon, default value is 0 which means the timer is + * Timeout in seconds for displaying 5G icon, default value is 0 which means the timer is * disabled. * * System UI will show the 5G icon and start a timer with the timeout from this config when the @@ -2940,8 +2898,6 @@ public class CarrierConfigManager { * * If 5G is reacquired during this timer, the timer is canceled and restarted when 5G is next * lost. Allows us to momentarily lose 5G without blinking the icon. - * - * @hide */ public static final String KEY_5G_ICON_DISPLAY_GRACE_PERIOD_SEC_INT = "5g_icon_display_grace_period_sec_int"; @@ -3106,8 +3062,6 @@ public class CarrierConfigManager { * signal bar of primary network. By default it will be false, meaning whenever data * is going over opportunistic network, signal bar will reflect signal strength and rat * icon of that network. - * - * @hide */ public static final String KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN = "always_show_primary_signal_bar_in_opportunistic_network_boolean"; @@ -3329,8 +3283,6 @@ public class CarrierConfigManager { /** * Determines whether wifi calling location privacy policy is shown. - * - * @hide */ public static final String KEY_SHOW_WFC_LOCATION_PRIVACY_POLICY_BOOL = "show_wfc_location_privacy_policy_bool"; @@ -3441,8 +3393,8 @@ public class CarrierConfigManager { "support_wps_over_ims_bool"; /** - * Holds the list of carrier certificate hashes. Note that each carrier has its own certificates - * @hide + * Holds the list of carrier certificate hashes. + * Note that each carrier has its own certificates. */ public static final String KEY_CARRIER_CERTIFICATE_STRING_ARRAY = "carrier_certificate_string_array"; @@ -3500,6 +3452,7 @@ public class CarrierConfigManager { sDefaults.putInt(KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT, 2); sDefaults.putInt(KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT, 2); sDefaults.putBoolean(KEY_CARRIER_FORCE_DISABLE_ETWS_CMAS_TEST_BOOL, false); + sDefaults.putBoolean(KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL, true); sDefaults.putBoolean(KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL, false); diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java index 31434c1d2adf..0cfb8c56320a 100644 --- a/telephony/java/android/telephony/PreciseDataConnectionState.java +++ b/telephony/java/android/telephony/PreciseDataConnectionState.java @@ -20,6 +20,9 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; +import android.compat.Compatibility; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.compat.annotation.UnsupportedAppUsage; import android.net.LinkProperties; import android.os.Build; @@ -31,8 +34,6 @@ import android.telephony.Annotation.DataState; import android.telephony.Annotation.NetworkType; import android.telephony.data.ApnSetting; -import dalvik.system.VMRuntime; - import java.util.Objects; @@ -134,6 +135,13 @@ public final class PreciseDataConnectionState implements Parcelable { } /** + * To check the SDK version for {@link PreciseDataConnectionState#getDataConnectionState}. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) + private static final long GET_DATA_CONNECTION_STATE_CODE_CHANGE = 147600208L; + + /** * Returns the state of data connection that supported the apn types returned by * {@link #getDataConnectionApnTypeBitMask()} * @@ -144,7 +152,7 @@ public final class PreciseDataConnectionState implements Parcelable { @SystemApi public @DataState int getDataConnectionState() { if (mState == TelephonyManager.DATA_DISCONNECTING - && VMRuntime.getRuntime().getTargetSdkVersion() < Build.VERSION_CODES.R) { + && !Compatibility.isChangeEnabled(GET_DATA_CONNECTION_STATE_CODE_CHANGE)) { return TelephonyManager.DATA_CONNECTED; } diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index 2c62d0667e19..2c8014e4a84d 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -16,8 +16,6 @@ package android.telephony; -import com.android.telephony.Rlog; - import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -36,6 +34,8 @@ import android.telephony.NetworkRegistrationInfo.Domain; import android.telephony.NetworkRegistrationInfo.NRState; import android.text.TextUtils; +import com.android.telephony.Rlog; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -353,6 +353,7 @@ public class ServiceState implements Parcelable { private String mOperatorAlphaLongRaw; private String mOperatorAlphaShortRaw; + private boolean mIsDataRoamingFromRegistration; private boolean mIsIwlanPreferred; /** @@ -438,6 +439,7 @@ public class ServiceState implements Parcelable { mNrFrequencyRange = s.mNrFrequencyRange; mOperatorAlphaLongRaw = s.mOperatorAlphaLongRaw; mOperatorAlphaShortRaw = s.mOperatorAlphaShortRaw; + mIsDataRoamingFromRegistration = s.mIsDataRoamingFromRegistration; mIsIwlanPreferred = s.mIsIwlanPreferred; } @@ -472,6 +474,7 @@ public class ServiceState implements Parcelable { mNrFrequencyRange = in.readInt(); mOperatorAlphaLongRaw = in.readString(); mOperatorAlphaShortRaw = in.readString(); + mIsDataRoamingFromRegistration = in.readBoolean(); mIsIwlanPreferred = in.readBoolean(); } @@ -499,6 +502,7 @@ public class ServiceState implements Parcelable { out.writeInt(mNrFrequencyRange); out.writeString(mOperatorAlphaLongRaw); out.writeString(mOperatorAlphaShortRaw); + out.writeBoolean(mIsDataRoamingFromRegistration); out.writeBoolean(mIsIwlanPreferred); } @@ -584,8 +588,8 @@ public class ServiceState implements Parcelable { */ @DuplexMode public int getDuplexMode() { - // only support LTE duplex mode - if (!isLte(getRilDataRadioTechnology())) { + // support LTE/NR duplex mode + if (!isPsOnlyTech(getRilDataRadioTechnology())) { return DUPLEX_MODE_UNKNOWN; } @@ -649,7 +653,9 @@ public class ServiceState implements Parcelable { } /** - * Get current data network roaming type + * Get whether the current data network is roaming. + * This value may be overwritten by resource overlay or carrier configuration. + * @see #getDataRoamingFromRegistration() to get the value from the network registration. * @return roaming type * @hide */ @@ -659,18 +665,25 @@ public class ServiceState implements Parcelable { } /** - * Get whether data network registration state is roaming + * Set whether the data network registration state is roaming. + * This should only be set to the roaming value received + * once the data registration phase has completed. + * @hide + */ + public void setDataRoamingFromRegistration(boolean dataRoaming) { + mIsDataRoamingFromRegistration = dataRoaming; + } + + /** + * Get whether data network registration state is roaming. + * This value is set directly from the modem and will not be overwritten + * by resource overlay or carrier configuration. * @return true if registration indicates roaming, false otherwise * @hide */ + @SystemApi public boolean getDataRoamingFromRegistration() { - final NetworkRegistrationInfo regState = getNetworkRegistrationInfo( - NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); - if (regState != null) { - return regState.getRegistrationState() - == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING; - } - return false; + return mIsDataRoamingFromRegistration; } /** @@ -873,6 +886,7 @@ public class ServiceState implements Parcelable { mNrFrequencyRange, mOperatorAlphaLongRaw, mOperatorAlphaShortRaw, + mIsDataRoamingFromRegistration, mIsIwlanPreferred); } } @@ -903,6 +917,7 @@ public class ServiceState implements Parcelable { && mNetworkRegistrationInfos.size() == s.mNetworkRegistrationInfos.size() && mNetworkRegistrationInfos.containsAll(s.mNetworkRegistrationInfos) && mNrFrequencyRange == s.mNrFrequencyRange + && mIsDataRoamingFromRegistration == s.mIsDataRoamingFromRegistration && mIsIwlanPreferred == s.mIsIwlanPreferred; } } @@ -1062,6 +1077,8 @@ public class ServiceState implements Parcelable { .append(", mNrFrequencyRange=").append(mNrFrequencyRange) .append(", mOperatorAlphaLongRaw=").append(mOperatorAlphaLongRaw) .append(", mOperatorAlphaShortRaw=").append(mOperatorAlphaShortRaw) + .append(", mIsDataRoamingFromRegistration=") + .append(mIsDataRoamingFromRegistration) .append(", mIsIwlanPreferred=").append(mIsIwlanPreferred) .append("}").toString(); } @@ -1102,6 +1119,7 @@ public class ServiceState implements Parcelable { } mOperatorAlphaLongRaw = null; mOperatorAlphaShortRaw = null; + mIsDataRoamingFromRegistration = false; mIsIwlanPreferred = false; } @@ -1624,7 +1642,8 @@ public class ServiceState implements Parcelable { * @return Current data network type * @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @SystemApi + @TestApi public @NetworkType int getDataNetworkType() { final NetworkRegistrationInfo iwlanRegInfo = getNetworkRegistrationInfo( NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WLAN); @@ -1717,9 +1736,10 @@ public class ServiceState implements Parcelable { } /** @hide */ - public static boolean isLte(int radioTechnology) { - return radioTechnology == RIL_RADIO_TECHNOLOGY_LTE || - radioTechnology == RIL_RADIO_TECHNOLOGY_LTE_CA; + public static boolean isPsOnlyTech(int radioTechnology) { + return radioTechnology == RIL_RADIO_TECHNOLOGY_LTE + || radioTechnology == RIL_RADIO_TECHNOLOGY_LTE_CA + || radioTechnology == RIL_RADIO_TECHNOLOGY_NR; } /** @hide */ diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java index 4f104f463487..fee6d3fc4795 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -25,11 +25,12 @@ import android.annotation.RequiresPermission; import android.annotation.SuppressAutoDoc; import android.annotation.SystemApi; import android.annotation.TestApi; -import android.app.ActivityThread; import android.app.PendingIntent; +import android.compat.Compatibility; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; -import android.content.pm.PackageManager; import android.database.CursorWindow; import android.net.Uri; import android.os.Build; @@ -281,6 +282,42 @@ public final class SmsManager { */ public static final int SMS_MESSAGE_PERIOD_NOT_SPECIFIED = -1; + /** @hide */ + @IntDef(prefix = { "PREMIUM_SMS_CONSENT" }, value = { + SmsManager.PREMIUM_SMS_CONSENT_UNKNOWN, + SmsManager.PREMIUM_SMS_CONSENT_ASK_USER, + SmsManager.PREMIUM_SMS_CONSENT_NEVER_ALLOW, + SmsManager.PREMIUM_SMS_CONSENT_ALWAYS_ALLOW + }) + @Retention(RetentionPolicy.SOURCE) + public @interface PremiumSmsConsent {} + + /** Premium SMS Consent for the package is unknown. This indicates that the user + * has not set a permission for this package, because this package has never tried + * to send a premium SMS. + * @hide + */ + @SystemApi + public static final int PREMIUM_SMS_CONSENT_UNKNOWN = 0; + + /** Default premium SMS Consent (ask user for each premium SMS sent). + * @hide + */ + @SystemApi + public static final int PREMIUM_SMS_CONSENT_ASK_USER = 1; + + /** Premium SMS Consent when the owner has denied the app from sending premium SMS. + * @hide + */ + @SystemApi + public static final int PREMIUM_SMS_CONSENT_NEVER_ALLOW = 2; + + /** Premium SMS Consent when the owner has allowed the app to send premium SMS. + * @hide + */ + @SystemApi + public static final int PREMIUM_SMS_CONSENT_ALWAYS_ALLOW = 3; + // result of asking the user for a subscription to perform an operation. private interface SubscriptionResolverResult { void onSuccess(int subId); @@ -387,7 +424,7 @@ public final class SmsManager { String destinationAddress, String scAddress, String text, PendingIntent sentIntent, PendingIntent deliveryIntent) { sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent, - true /* persistMessage*/, ActivityThread.currentPackageName()); + true /* persistMessage*/, null); } /** @@ -597,7 +634,7 @@ public final class SmsManager { String destinationAddress, String scAddress, String text, PendingIntent sentIntent, PendingIntent deliveryIntent) { sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent, - false /* persistMessage */, ActivityThread.currentPackageName()); + false /* persistMessage */, null); } private void sendTextMessageInternal( @@ -640,7 +677,7 @@ public final class SmsManager { ISms iSms = getISmsServiceOrThrow(); if (iSms != null) { iSms.sendTextForSubscriberWithOptions(subId, - ActivityThread.currentPackageName(), destinationAddress, + null, destinationAddress, scAddress, text, sentIntent, deliveryIntent, persistMessage, finalPriority, expectMore, finalValidity); @@ -662,7 +699,7 @@ public final class SmsManager { ISms iSms = getISmsServiceOrThrow(); if (iSms != null) { iSms.sendTextForSubscriberWithOptions(getSubscriptionId(), - ActivityThread.currentPackageName(), destinationAddress, + null, destinationAddress, scAddress, text, sentIntent, deliveryIntent, persistMessage, finalPriority, expectMore, finalValidity); @@ -884,7 +921,7 @@ public final class SmsManager { String destinationAddress, String scAddress, ArrayList<String> parts, ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents) { sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents, - deliveryIntents, true /* persistMessage*/, ActivityThread.currentPackageName()); + deliveryIntents, true /* persistMessage*/, null); } /** @@ -901,8 +938,9 @@ public final class SmsManager { * subscription. * </p> * - * @param packageName serves as the default package name if - * {@link ActivityThread#currentPackageName()} is null. + * @param packageName serves as the default package name if the package name that is + * associated with the user id is null. + * * @hide */ @SystemApi @@ -912,9 +950,7 @@ public final class SmsManager { @NonNull List<String> parts, @Nullable List<PendingIntent> sentIntents, @Nullable List<PendingIntent> deliveryIntents, @NonNull String packageName) { sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents, - deliveryIntents, true /* persistMessage*/, - ActivityThread.currentPackageName() == null - ? packageName : ActivityThread.currentPackageName()); + deliveryIntents, true /* persistMessage*/, packageName); } private void sendMultipartTextMessageInternal( @@ -1015,7 +1051,7 @@ public final class SmsManager { String destinationAddress, String scAddress, List<String> parts, List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents) { sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents, - deliveryIntents, false /* persistMessage*/, ActivityThread.currentPackageName()); + deliveryIntents, false /* persistMessage*/, null); } /** @@ -1177,7 +1213,7 @@ public final class SmsManager { ISms iSms = getISmsServiceOrThrow(); if (iSms != null) { iSms.sendMultipartTextForSubscriberWithOptions(subId, - ActivityThread.currentPackageName(), destinationAddress, + null, destinationAddress, scAddress, parts, sentIntents, deliveryIntents, persistMessage, finalPriority, expectMore, finalValidity); } @@ -1199,7 +1235,7 @@ public final class SmsManager { ISms iSms = getISmsServiceOrThrow(); if (iSms != null) { iSms.sendMultipartTextForSubscriberWithOptions(getSubscriptionId(), - ActivityThread.currentPackageName(), destinationAddress, + null, destinationAddress, scAddress, parts, sentIntents, deliveryIntents, persistMessage, finalPriority, expectMore, finalValidity); } @@ -1330,7 +1366,7 @@ public final class SmsManager { public void onSuccess(int subId) { try { ISms iSms = getISmsServiceOrThrow(); - iSms.sendDataForSubscriber(subId, ActivityThread.currentPackageName(), + iSms.sendDataForSubscriber(subId, null, destinationAddress, scAddress, destinationPort & 0xFFFF, data, sentIntent, deliveryIntent); } catch (RemoteException e) { @@ -1456,7 +1492,6 @@ public final class SmsManager { private void resolveSubscriptionForOperation(SubscriptionResolverResult resolverResult) { int subId = getSubscriptionId(); boolean isSmsSimPickActivityNeeded = false; - final Context context = ActivityThread.currentApplication().getApplicationContext(); try { ISms iSms = getISmsService(); if (iSms != null) { @@ -1478,14 +1513,14 @@ public final class SmsManager { return; } // We need to ask the user pick an appropriate subid for the operation. - Log.d(TAG, "resolveSubscriptionForOperation isSmsSimPickActivityNeeded is true for package " - + context.getPackageName()); + Log.d(TAG, "resolveSubscriptionForOperation isSmsSimPickActivityNeeded is true for calling" + + " package. "); try { // Create the SMS pick activity and call back once the activity is complete. Can't do // it here because we do not have access to the activity context that is performing this // operation. // Requires that the calling process has the SEND_SMS permission. - getITelephony().enqueueSmsPickResult(context.getOpPackageName(), + getITelephony().enqueueSmsPickResult(null, new IIntegerConsumer.Stub() { @Override public void accept(int subId) { @@ -1503,6 +1538,13 @@ public final class SmsManager { } } + /** + * To check the SDK version for SmsManager.sendResolverResult method. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.P) + private static final long GET_TARGET_SDK_VERSION_CODE_CHANGE = 145147528L; + private void sendResolverResult(SubscriptionResolverResult resolverResult, int subId, boolean pickActivityShown) { if (SubscriptionManager.isValidSubscriptionId(subId)) { @@ -1510,7 +1552,8 @@ public final class SmsManager { return; } - if (getTargetSdkVersion() <= Build.VERSION_CODES.P && !pickActivityShown) { + if (!Compatibility.isChangeEnabled(GET_TARGET_SDK_VERSION_CODE_CHANGE) + && !pickActivityShown) { // Do not fail, return a success with an INVALID subid for apps targeting P or below // that tried to perform an operation and the SMS disambiguation dialog was never shown, // as these applications may not have been written to handle the failure case properly. @@ -1523,19 +1566,6 @@ public final class SmsManager { } } - private static int getTargetSdkVersion() { - final Context context = ActivityThread.currentApplication().getApplicationContext(); - int targetSdk; - try { - targetSdk = context.getPackageManager().getApplicationInfo( - context.getOpPackageName(), 0).targetSdkVersion; - } catch (PackageManager.NameNotFoundException e) { - // Default to old behavior if we can not find this. - targetSdk = -1; - } - return targetSdk; - } - private static ITelephony getITelephony() { ITelephony binder = ITelephony.Stub.asInterface( TelephonyFrameworkInitializer @@ -1621,7 +1651,7 @@ public final class SmsManager { ISms iSms = getISmsService(); if (iSms != null) { success = iSms.copyMessageToIccEfForSubscriber(getSubscriptionId(), - ActivityThread.currentPackageName(), + null, status, pdu, smsc); } } catch (RemoteException ex) { @@ -1662,7 +1692,7 @@ public final class SmsManager { ISms iSms = getISmsService(); if (iSms != null) { success = iSms.updateMessageOnIccEfForSubscriber(getSubscriptionId(), - ActivityThread.currentPackageName(), + null, messageIndex, STATUS_ON_ICC_FREE, null /* pdu */); } } catch (RemoteException ex) { @@ -1705,7 +1735,7 @@ public final class SmsManager { ISms iSms = getISmsService(); if (iSms != null) { success = iSms.updateMessageOnIccEfForSubscriber(getSubscriptionId(), - ActivityThread.currentPackageName(), + null, messageIndex, newStatus, pdu); } } catch (RemoteException ex) { @@ -1757,7 +1787,7 @@ public final class SmsManager { if (iSms != null) { records = iSms.getAllMessagesFromIccEfForSubscriber( getSubscriptionId(), - ActivityThread.currentPackageName()); + null); } } catch (RemoteException ex) { // ignore it @@ -2581,7 +2611,7 @@ public final class SmsManager { try { ISms iccSms = getISmsServiceOrThrow(); return iccSms.createAppSpecificSmsToken(getSubscriptionId(), - ActivityThread.currentPackageName(), intent); + null, intent); } catch (RemoteException ex) { ex.rethrowFromSystemServer(); @@ -2701,7 +2731,7 @@ public final class SmsManager { try { ISms iccSms = getISmsServiceOrThrow(); return iccSms.createAppSpecificSmsTokenWithPackageInfo(getSubscriptionId(), - ActivityThread.currentPackageName(), prefixes, intent); + null, prefixes, intent); } catch (RemoteException ex) { ex.rethrowFromSystemServer(); @@ -2792,7 +2822,7 @@ public final class SmsManager { ISms iccISms = getISmsServiceOrThrow(); if (iccISms != null) { return iccISms.checkSmsShortCodeDestination(getSubscriptionId(), - ActivityThread.currentPackageName(), null, destAddress, countryIso); + null, null, destAddress, countryIso); } } catch (RemoteException e) { Log.e(TAG, "checkSmsShortCodeDestination() RemoteException", e); @@ -2828,7 +2858,7 @@ public final class SmsManager { ISms iSms = getISmsService(); if (iSms != null) { smsc = iSms.getSmscAddressFromIccEfForSubscriber( - getSubscriptionId(), ActivityThread.currentPackageName()); + getSubscriptionId(), null); } } catch (RemoteException ex) { // ignore it @@ -2862,11 +2892,60 @@ public final class SmsManager { ISms iSms = getISmsService(); if (iSms != null) { return iSms.setSmscAddressOnIccEfForSubscriber( - smsc, getSubscriptionId(), ActivityThread.currentPackageName()); + smsc, getSubscriptionId(), null); } } catch (RemoteException ex) { // ignore it } return false; } + + /** + * Gets the premium SMS permission for the specified package. If the package has never + * been seen before, the default {@link SmsManager#PREMIUM_SMS_PERMISSION_ASK_USER} + * will be returned. + * @param packageName the name of the package to query permission + * @return one of {@link SmsManager#PREMIUM_SMS_CONSENT_UNKNOWN}, + * {@link SmsManager#PREMIUM_SMS_CONSENT_ASK_USER}, + * {@link SmsManager#PREMIUM_SMS_CONSENT_NEVER_ALLOW}, or + * {@link SmsManager#PREMIUM_SMS_CONSENT_ALWAYS_ALLOW} + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public @PremiumSmsConsent int getPremiumSmsConsent(@NonNull String packageName) { + int permission = 0; + try { + ISms iSms = getISmsService(); + if (iSms != null) { + permission = iSms.getPremiumSmsPermission(packageName); + } + } catch (RemoteException e) { + Log.e(TAG, "getPremiumSmsPermission() RemoteException", e); + } + return permission; + } + + /** + * Sets the premium SMS permission for the specified package and save the value asynchronously + * to persistent storage. + * @param packageName the name of the package to set permission + * @param permission one of {@link SmsManager#PREMIUM_SMS_CONSENT_ASK_USER}, + * {@link SmsManager#PREMIUM_SMS_CONSENT_NEVER_ALLOW}, or + * {@link SmsManager#PREMIUM_SMS_CONSENT_ALWAYS_ALLOW} + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void setPremiumSmsConsent( + @NonNull String packageName, @PremiumSmsConsent int permission) { + try { + ISms iSms = getISmsService(); + if (iSms != null) { + iSms.setPremiumSmsPermission(packageName, permission); + } + } catch (RemoteException e) { + Log.e(TAG, "setPremiumSmsPermission() RemoteException", e); + } + } } diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java index c217b8b83c26..eefbd44ad6de 100644 --- a/telephony/java/android/telephony/SmsMessage.java +++ b/telephony/java/android/telephony/SmsMessage.java @@ -20,8 +20,13 @@ import com.android.telephony.Rlog; import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA; +import android.Manifest; +import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.StringDef; +import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.res.Resources; import android.os.Binder; @@ -31,8 +36,10 @@ import com.android.internal.telephony.GsmAlphabet; import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails; import com.android.internal.telephony.Sms7BitEncodingTranslator; import com.android.internal.telephony.SmsConstants; +import com.android.internal.telephony.SmsHeader; import com.android.internal.telephony.SmsMessageBase; import com.android.internal.telephony.SmsMessageBase.SubmitPduBase; +import com.android.internal.telephony.cdma.sms.UserData; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -56,6 +63,16 @@ public class SmsMessage { UNKNOWN, CLASS_0, CLASS_1, CLASS_2, CLASS_3; } + /** @hide */ + @IntDef(prefix = { "ENCODING_" }, value = { + ENCODING_UNKNOWN, + ENCODING_7BIT, + ENCODING_8BIT, + ENCODING_16BIT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface EncodingSize {} + /** User data text encoding code unit size */ public static final int ENCODING_UNKNOWN = 0; public static final int ENCODING_7BIT = 1; @@ -315,6 +332,34 @@ public class SmsMessage { } /** + * Create an SmsMessage from a native SMS-Submit PDU, specified by Bluetooth Message Access + * Profile Specification v1.4.2 5.8. + * This is used by Bluetooth MAP profile to decode message when sending non UTF-8 SMS messages. + * + * @param data Message data. + * @param isCdma Indicates weather the type of the SMS is CDMA. + * @return An SmsMessage representing the message. + * + * @hide + */ + @SystemApi + @Nullable + public static SmsMessage createFromNativeSmsSubmitPdu(@NonNull byte[] data, boolean isCdma) { + SmsMessageBase wrappedMessage; + + if (isCdma) { + wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromEfRecord( + 0, data); + } else { + // Bluetooth uses its own method to decode GSM PDU so this part is not called. + wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromEfRecord( + 0, data); + } + + return wrappedMessage != null ? new SmsMessage(wrappedMessage) : null; + } + + /** * Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the * length in bytes (not hex chars) less the SMSC header * @@ -633,6 +678,83 @@ public class SmsMessage { } /** + * Get an SMS-SUBMIT PDU's encoded message. + * This is used by Bluetooth MAP profile to handle long non UTF-8 SMS messages. + * + * @param isTypeGsm true when message's type is GSM, false when type is CDMA + * @param destinationAddress the address of the destination for the message + * @param message message content + * @param encoding User data text encoding code unit size + * @param languageTable GSM national language table to use, specified by 3GPP + * 23.040 9.2.3.24.16 + * @param languageShiftTable GSM national language shift table to use, specified by 3GPP + * 23.040 9.2.3.24.15 + * @param refNumber parameter to create SmsHeader + * @param seqNumber parameter to create SmsHeader + * @param msgCount parameter to create SmsHeader + * @return a byte[] containing the encoded message + * + * @hide + */ + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @SystemApi + @NonNull + public static byte[] getSubmitPduEncodedMessage(boolean isTypeGsm, + @NonNull String destinationAddress, + @NonNull String message, + @EncodingSize int encoding, int languageTable, + int languageShiftTable, int refNumber, + int seqNumber, int msgCount) { + byte[] data; + SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef(); + concatRef.refNumber = refNumber; + concatRef.seqNumber = seqNumber; // 1-based sequence + concatRef.msgCount = msgCount; + // We currently set this to true since our messaging app will never + // send more than 255 parts (it converts the message to MMS well before that). + // However, we should support 3rd party messaging apps that might need 16-bit + // references + // Note: It's not sufficient to just flip this bit to true; it will have + // ripple effects (several calculations assume 8-bit ref). + concatRef.isEightBits = true; + SmsHeader smsHeader = new SmsHeader(); + smsHeader.concatRef = concatRef; + + /* Depending on the type, call either GSM or CDMA getSubmitPdu(). The encoding + * will be determined(again) by getSubmitPdu(). + * All packets need to be encoded using the same encoding, as the bMessage + * only have one filed to describe the encoding for all messages in a concatenated + * SMS... */ + if (encoding == ENCODING_7BIT) { + smsHeader.languageTable = languageTable; + smsHeader.languageShiftTable = languageShiftTable; + } + + if (isTypeGsm) { + data = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(null, + destinationAddress, message, false, + SmsHeader.toByteArray(smsHeader), encoding, languageTable, + languageShiftTable).encodedMessage; + } else { // SMS_TYPE_CDMA + UserData uData = new UserData(); + uData.payloadStr = message; + uData.userDataHeader = smsHeader; + if (encoding == ENCODING_7BIT) { + uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET; + } else { // assume UTF-16 + uData.msgEncoding = UserData.ENCODING_UNICODE_16; + } + uData.msgEncodingSet = true; + data = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu( + destinationAddress, uData, false).encodedMessage; + } + if (data == null) { + return new byte[0]; + } + return data; + } + + /** * Returns the address of the SMS service center that relayed this message * or null if there is none. */ diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 4510fede4e8e..b42ce35aa79c 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -16,8 +16,6 @@ package android.telephony; -import com.android.telephony.Rlog; - import static android.net.NetworkPolicyManager.OVERRIDE_CONGESTED; import static android.net.NetworkPolicyManager.OVERRIDE_UNMETERED; @@ -67,6 +65,7 @@ import com.android.internal.telephony.ISub; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.util.HandlerExecutor; import com.android.internal.util.Preconditions; +import com.android.telephony.Rlog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -2233,6 +2232,7 @@ public class SubscriptionManager { } else { logd("putPhoneIdAndSubIdExtra: no valid subs"); intent.putExtra(PhoneConstants.PHONE_KEY, phoneId); + intent.putExtra(EXTRA_SLOT_INDEX, phoneId); } } @@ -2240,10 +2240,9 @@ public class SubscriptionManager { @UnsupportedAppUsage public static void putPhoneIdAndSubIdExtra(Intent intent, int phoneId, int subId) { if (VDBG) logd("putPhoneIdAndSubIdExtra: phoneId=" + phoneId + " subId=" + subId); - intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId); - intent.putExtra(EXTRA_SUBSCRIPTION_INDEX, subId); intent.putExtra(EXTRA_SLOT_INDEX, phoneId); intent.putExtra(PhoneConstants.PHONE_KEY, phoneId); + putSubscriptionIdExtra(intent, subId); } /** @@ -3489,4 +3488,19 @@ public class SubscriptionManager { } return SubscriptionManager.INVALID_SUBSCRIPTION_ID; } + + /** + * Helper method that puts a subscription id on an intent with the constants: + * PhoneConstant.SUBSCRIPTION_KEY and SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX. + * Both constants are used to support backwards compatibility. Once we know we got all places, + * we can remove PhoneConstants.SUBSCRIPTION_KEY. + * @param intent Intent to put sub id on. + * @param subId SubscriptionId to put on intent. + * + * @hide + */ + public static void putSubscriptionIdExtra(Intent intent, int subId) { + intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId); + intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId); + } } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 132da3d83f68..56ca8c7790cb 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -37,7 +37,6 @@ import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.annotation.WorkerThread; -import android.app.ActivityThread; import android.app.PendingIntent; import android.compat.Compatibility; import android.compat.annotation.ChangeId; @@ -109,8 +108,6 @@ import com.android.internal.telephony.RILConstants; import com.android.internal.telephony.SmsApplication; import com.android.telephony.Rlog; -import dalvik.system.VMRuntime; - import java.io.FileInputStream; import java.io.IOException; import java.lang.annotation.Retention; @@ -382,8 +379,17 @@ public class TelephonyManager { // effort and get the context from the current activity thread. if (mContext != null) { return mContext.getOpPackageName(); + } else { + ITelephony telephony = getITelephony(); + if (telephony == null) return null; + try { + return telephony.getCurrentPackageName(); + } catch (RemoteException ex) { + return null; + } catch (NullPointerException ex) { + return null; + } } - return ActivityThread.currentOpPackageName(); } private String getFeatureId() { @@ -1106,6 +1112,16 @@ public class TelephonyManager { */ public static final int CDMA_ROAMING_MODE_ANY = 2; + /** @hide */ + @IntDef(prefix = { "CDMA_ROAMING_MODE_" }, value = { + CDMA_ROAMING_MODE_RADIO_DEFAULT, + CDMA_ROAMING_MODE_HOME, + CDMA_ROAMING_MODE_AFFILIATED, + CDMA_ROAMING_MODE_ANY + }) + @Retention(RetentionPolicy.SOURCE) + public @interface CdmaRoamingMode{} + /** * An unknown carrier id. It could either be subscription unavailable or the subscription * carrier cannot be recognized. Unrecognized carriers here means @@ -1446,7 +1462,8 @@ public class TelephonyManager { /** * <p>Broadcast Action: The emergency callback mode is changed. * <ul> - * <li><em>phoneinECMState</em> - A boolean value,true=phone in ECM, false=ECM off</li> + * <li><em>EXTRA_PHONE_IN_ECM_STATE</em> - A boolean value,true=phone in ECM, + * false=ECM off</li> * </ul> * <p class="note"> * You can <em>not</em> receive this through components declared @@ -1456,12 +1473,25 @@ public class TelephonyManager { * * <p class="note">This is a protected intent that can only be sent by the system. * + * @see #EXTRA_PHONE_IN_ECM_STATE + * * @hide */ @SystemApi @SuppressLint("ActionValue") - public static final String ACTION_EMERGENCY_CALLBACK_MODE_CHANGED - = "android.intent.action.EMERGENCY_CALLBACK_MODE_CHANGED"; + public static final String ACTION_EMERGENCY_CALLBACK_MODE_CHANGED = + "android.intent.action.EMERGENCY_CALLBACK_MODE_CHANGED"; + + + /** + * Extra included in {@link #ACTION_EMERGENCY_CALLBACK_MODE_CHANGED}. + * Indicates whether the phone is in an emergency phone state. + * + * @hide + */ + @SystemApi + public static final String EXTRA_PHONE_IN_ECM_STATE = + "android.telephony.extra.PHONE_IN_ECM_STATE"; /** * <p>Broadcast Action: when data connections get redirected with validation failure. @@ -1653,8 +1683,8 @@ public class TelephonyManager { /** * <p>Broadcast Action: The emergency call state is changed. * <ul> - * <li><em>phoneInEmergencyCall</em> - A boolean value, true if phone in emergency call, - * false otherwise</li> + * <li><em>EXTRA_PHONE_IN_EMERGENCY_CALL</em> - A boolean value, true if phone in emergency + * call, false otherwise</li> * </ul> * <p class="note"> * You can <em>not</em> receive this through components declared @@ -1664,12 +1694,25 @@ public class TelephonyManager { * * <p class="note">This is a protected intent that can only be sent by the system. * + * @see #EXTRA_PHONE_IN_EMERGENCY_CALL + * * @hide */ @SystemApi @SuppressLint("ActionValue") - public static final String ACTION_EMERGENCY_CALL_STATE_CHANGED - = "android.intent.action.EMERGENCY_CALL_STATE_CHANGED"; + public static final String ACTION_EMERGENCY_CALL_STATE_CHANGED = + "android.intent.action.EMERGENCY_CALL_STATE_CHANGED"; + + + /** + * Extra included in {@link #ACTION_EMERGENCY_CALL_STATE_CHANGED}. + * It indicates whether the phone is making an emergency call. + * + * @hide + */ + @SystemApi + public static final String EXTRA_PHONE_IN_EMERGENCY_CALL = + "android.telephony.extra.PHONE_IN_EMERGENCY_CALL"; /** * <p>Broadcast Action: It indicates the Emergency callback mode blocks datacall/sms @@ -1682,8 +1725,8 @@ public class TelephonyManager { * @hide */ @SystemApi - public static final String ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS - = "android.telephony.action.SHOW_NOTICE_ECM_BLOCK_OTHERS"; + public static final String ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS = + "android.telephony.action.SHOW_NOTICE_ECM_BLOCK_OTHERS"; /** * Broadcast Action: The default data subscription has changed in a multi-SIM device. @@ -1696,8 +1739,8 @@ public class TelephonyManager { */ @SystemApi @SuppressLint("ActionValue") - public static final String ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED - = "android.intent.action.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED"; + public static final String ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED = + "android.intent.action.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED"; /** * Broadcast Action: The default voice subscription has changed in a mult-SIm device. @@ -1710,8 +1753,8 @@ public class TelephonyManager { */ @SystemApi @SuppressLint("ActionValue") - public static final String ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED - = "android.intent.action.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED"; + public static final String ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED = + "android.intent.action.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED"; /** * Broadcast Action: This triggers a client initiated OMA-DM session to the OMA server. @@ -1724,8 +1767,8 @@ public class TelephonyManager { */ @SystemApi @SuppressLint("ActionValue") - public static final String ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE - = "com.android.omadm.service.CONFIGURATION_UPDATE"; + public static final String ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE = + "com.android.omadm.service.CONFIGURATION_UPDATE"; // // @@ -2241,6 +2284,16 @@ public class TelephonyManager { public static final int PHONE_TYPE_CDMA = PhoneConstants.PHONE_TYPE_CDMA; /** Phone is via SIP. */ public static final int PHONE_TYPE_SIP = PhoneConstants.PHONE_TYPE_SIP; + /** Phone is via IMS. */ + public static final int PHONE_TYPE_IMS = PhoneConstants.PHONE_TYPE_IMS; + + /** + * Phone is via Third Party. + * + * @hide + */ + @SystemApi + public static final int PHONE_TYPE_THIRD_PARTY = PhoneConstants.PHONE_TYPE_THIRD_PARTY; /** * Returns the current phone type. @@ -5411,6 +5464,13 @@ public class TelephonyManager { public static final int DATA_DISCONNECTING = 4; /** + * To check the SDK version for {@link TelephonyManager#getDataState}. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) + private static final long GET_DATA_STATE_CODE_CHANGE = 147600208L; + + /** * Returns a constant indicating the current data connection state * (cellular). * @@ -5428,7 +5488,7 @@ public class TelephonyManager { int state = telephony.getDataStateForSubId( getSubId(SubscriptionManager.getActiveDataSubscriptionId())); if (state == TelephonyManager.DATA_DISCONNECTING - && VMRuntime.getRuntime().getTargetSdkVersion() < Build.VERSION_CODES.R) { + && !Compatibility.isChangeEnabled(GET_DATA_STATE_CODE_CHANGE)) { return TelephonyManager.DATA_CONNECTED; } @@ -5490,6 +5550,13 @@ public class TelephonyManager { // /** + * To check the SDK version for {@link TelephonyManager#listen}. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.P) + private static final long LISTEN_CODE_CHANGE = 147600208L; + + /** * Registers a listener object to receive notification of changes * in specified telephony states. * <p> @@ -5528,7 +5595,7 @@ public class TelephonyManager { // subId from PhoneStateListener is deprecated Q on forward, use the subId from // TelephonyManager instance. keep using subId from PhoneStateListener for pre-Q. int subId = mSubId; - if (VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.Q) { + if (Compatibility.isChangeEnabled(LISTEN_CODE_CHANGE)) { // since mSubId in PhoneStateListener is deprecated from Q on forward, this is // the only place to set mSubId and its for "informational" only. // TODO: remove this once we completely get rid of mSubId in PhoneStateListener @@ -7600,6 +7667,18 @@ public class TelephonyManager { RILConstants.NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA; /** + * The default preferred network mode constant. + * + * <p> This constant is used in case of nothing is set in + * TelephonyProperties#default_network(). + * + * @hide + */ + @SystemApi + public static final int DEFAULT_PREFERRED_NETWORK_MODE = + RILConstants.DEFAULT_PREFERRED_NETWORK_MODE; + + /** * Get the preferred network type. * Used for device configuration by some CDMA operators. * @@ -8935,8 +9014,9 @@ public class TelephonyManager { * * @hide */ - @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) - public int getCdmaRoamingMode() { + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public @CdmaRoamingMode int getCdmaRoamingMode() { int mode = CDMA_ROAMING_MODE_RADIO_DEFAULT; try { ITelephony telephony = getITelephony(); @@ -8963,8 +9043,9 @@ public class TelephonyManager { * * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) - public boolean setCdmaRoamingMode(int mode) { + public boolean setCdmaRoamingMode(@CdmaRoamingMode int mode) { try { ITelephony telephony = getITelephony(); if (telephony != null) { @@ -8976,6 +9057,36 @@ public class TelephonyManager { return false; } + /** @hide */ + @IntDef(flag = true, prefix = { "CDMA_SUBSCRIPTION_" }, value = { + CDMA_SUBSCRIPTION_UNKNOWN, + CDMA_SUBSCRIPTION_RUIM_SIM, + CDMA_SUBSCRIPTION_NV + }) + @Retention(RetentionPolicy.SOURCE) + public @interface CdmaSubscription{} + + /** Used for CDMA subscription mode, it'll be UNKNOWN if there is no Subscription source. + * @hide + */ + @SystemApi + public static final int CDMA_SUBSCRIPTION_UNKNOWN = -1; + + /** Used for CDMA subscription mode: RUIM/SIM (default) + * @hide + */ + @SystemApi + public static final int CDMA_SUBSCRIPTION_RUIM_SIM = 0; + + /** Used for CDMA subscription mode: NV -> non-volatile memory + * @hide + */ + @SystemApi + public static final int CDMA_SUBSCRIPTION_NV = 1; + + /** @hide */ + public static final int PREFERRED_CDMA_SUBSCRIPTION = CDMA_SUBSCRIPTION_RUIM_SIM; + /** * Sets the subscription mode for CDMA phone to the given mode {@code mode}. * @@ -8983,14 +9094,15 @@ public class TelephonyManager { * * @return {@code true} if successed. * - * @see Phone#CDMA_SUBSCRIPTION_UNKNOWN - * @see Phone#CDMA_SUBSCRIPTION_RUIM_SIM - * @see Phone#CDMA_SUBSCRIPTION_NV + * @see #CDMA_SUBSCRIPTION_UNKNOWN + * @see #CDMA_SUBSCRIPTION_RUIM_SIM + * @see #CDMA_SUBSCRIPTION_NV * * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) - public boolean setCdmaSubscriptionMode(int mode) { + public boolean setCdmaSubscriptionMode(@CdmaSubscription int mode) { try { ITelephony telephony = getITelephony(); if (telephony != null) { @@ -10603,6 +10715,7 @@ public class TelephonyManager { * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}. * * @param enabled control enable or disable carrier data. + * @see #resetAllCarrierActions() * @hide */ @SystemApi @@ -10629,6 +10742,7 @@ public class TelephonyManager { * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}. * * @param enabled control enable or disable radio. + * @see #resetAllCarrierActions() * @hide */ @SystemApi @@ -10655,6 +10769,7 @@ public class TelephonyManager { * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}. * * @param report control start/stop reporting network status. + * @see #resetAllCarrierActions() * @hide */ @SystemApi diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java index 7ffba7abffd6..9b739d3acbbc 100644 --- a/telephony/java/android/telephony/ims/ProvisioningManager.java +++ b/telephony/java/android/telephony/ims/ProvisioningManager.java @@ -25,8 +25,6 @@ import android.annotation.StringDef; import android.annotation.SystemApi; import android.annotation.TestApi; import android.annotation.WorkerThread; -import android.content.pm.IPackageManager; -import android.content.pm.PackageManager; import android.os.Binder; import android.os.RemoteException; import android.telephony.CarrierConfigManager; @@ -34,6 +32,7 @@ import android.telephony.SubscriptionManager; import android.telephony.TelephonyFrameworkInitializer; import android.telephony.ims.aidl.IImsConfigCallback; import android.telephony.ims.feature.MmTelFeature; +import android.telephony.ims.feature.RcsFeature; import android.telephony.ims.stub.ImsConfigImplBase; import android.telephony.ims.stub.ImsRegistrationImplBase; @@ -381,10 +380,6 @@ public class ProvisioningManager { @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(@NonNull @CallbackExecutor Executor executor, @NonNull Callback callback) throws ImsException { - if (!isImsAvailableOnDevice()) { - throw new ImsException("IMS not available on device.", - ImsException.CODE_ERROR_UNSUPPORTED_OPERATION); - } callback.setExecutor(executor); try { getITelephony().registerImsProvisioningChangedCallback(mSubId, callback.getBinder()); @@ -546,41 +541,75 @@ public class ProvisioningManager { } /** + * Get the provisioning status for the IMS RCS capability specified. + * + * If provisioning is not required for the queried + * {@link RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag} this method will always return + * {@code true}. + * + * @see CarrierConfigManager#KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL + * @return true if the device is provisioned for the capability or does not require + * provisioning, false if the capability does require provisioning and has not been + * provisioned yet. + */ + @WorkerThread + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public boolean getRcsProvisioningStatusForCapability( + @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability) { + try { + return getITelephony().getRcsProvisioningStatusForCapability(mSubId, capability); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * Set the provisioning status for the IMS RCS capability using the specified subscription. + * + * Provisioning may or may not be required, depending on the carrier configuration. If + * provisioning is not required for the carrier associated with this subscription or the device + * does not support the capability/technology combination specified, this operation will be a + * no-op. + * + * @see CarrierConfigManager#KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL + * @param isProvisioned true if the device is provisioned for the RCS capability specified, + * false otherwise. + */ + @WorkerThread + @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) + public void setRcsProvisioningStatusForCapability( + @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability, + boolean isProvisioned) { + try { + getITelephony().setRcsProvisioningStatusForCapability(mSubId, capability, + isProvisioned); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** * Notify the framework that an RCS autoconfiguration XML file has been received for * provisioning. + * <p> + * Requires Permission: Manifest.permission.MODIFY_PHONE_STATE or that the calling app has + * carrier privileges (see {@link #hasCarrierPrivileges}). * @param config The XML file to be read. ASCII/UTF8 encoded text if not compressed. * @param isCompressed The XML file is compressed in gzip format and must be decompressed * before being read. - * @hide + * */ @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) public void notifyRcsAutoConfigurationReceived(@NonNull byte[] config, boolean isCompressed) { if (config == null) { throw new IllegalArgumentException("Must include a non-null config XML file."); } - // TODO: Connect to ImsConfigImplBase. - throw new UnsupportedOperationException("notifyRcsAutoConfigurationReceived is not" - + "supported"); - } - - private static boolean isImsAvailableOnDevice() { - IPackageManager pm = IPackageManager.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getPackageManagerServiceRegisterer() - .get()); - if (pm == null) { - // For some reason package manger is not available.. This will fail internally anyways, - // so do not throw error and allow. - return true; - } try { - return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS, 0); + getITelephony().notifyRcsAutoConfigurationReceived(mSubId, config, isCompressed); } catch (RemoteException e) { - // For some reason package manger is not available.. This will fail internally anyways, - // so do not throw error and allow. + throw e.rethrowAsRuntimeException(); } - return true; + } private static ITelephony getITelephony() { diff --git a/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl b/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl index 53e459697958..57206c9f059a 100644 --- a/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl @@ -40,4 +40,5 @@ interface IImsConfig { // Return result code defined in ImsConfig#OperationStatusConstants int setConfigString(int item, String value); void updateImsCarrierConfigs(in PersistableBundle bundle); + void notifyRcsAutoConfigurationReceived(in byte[] config, boolean isCompressed); } diff --git a/telephony/java/android/telephony/ims/aidl/IRcsFeatureListener.aidl b/telephony/java/android/telephony/ims/aidl/IRcsFeatureListener.aidl index 881b4776b25b..70cf651d3924 100644 --- a/telephony/java/android/telephony/ims/aidl/IRcsFeatureListener.aidl +++ b/telephony/java/android/telephony/ims/aidl/IRcsFeatureListener.aidl @@ -32,11 +32,11 @@ interface IRcsFeatureListener { oneway void onNetworkResponse(int code, in String reason, int operationToken); oneway void onCapabilityRequestResponsePresence(in List<RcsContactUceCapability> infos, int operationToken); - oneway void onNotifyUpdateCapabilities(); + oneway void onNotifyUpdateCapabilities(int publishTriggerType); oneway void onUnpublish(); // RcsSipOptionsImplBase specific oneway void onCapabilityRequestResponseOptions(int code, in String reason, in RcsContactUceCapability info, int operationToken); oneway void onRemoteCapabilityRequest(in Uri contactUri, in RcsContactUceCapability remoteInfo, int operationToken); -}
\ No newline at end of file +} diff --git a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java index 60cf216627a3..6a2638bc7221 100644 --- a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java @@ -17,6 +17,7 @@ package android.telephony.ims.stub; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.SystemApi; import android.annotation.TestApi; import android.content.Context; @@ -200,6 +201,12 @@ public class ImsConfigImplBase { } } + @Override + public void notifyRcsAutoConfigurationReceived(byte[] config, boolean isCompressed) + throws RemoteException { + getImsConfigImpl().notifyRcsAutoConfigurationReceived(config, isCompressed); + } + private void notifyImsConfigChanged(int item, int value) throws RemoteException { getImsConfigImpl().notifyConfigChanged(item, value); } @@ -358,9 +365,9 @@ public class ImsConfigImplBase { * @param config The XML file to be read, if not compressed, it should be in ASCII/UTF8 format. * @param isCompressed The XML file is compressed in gzip format and must be decompressed * before being read. - * @hide + * */ - public void notifyRcsAutoConfigurationReceived(byte[] config, boolean isCompressed) { + public void notifyRcsAutoConfigurationReceived(@NonNull byte[] config, boolean isCompressed) { } /** diff --git a/telephony/java/android/telephony/ims/stub/RcsPresenceExchangeImplBase.java b/telephony/java/android/telephony/ims/stub/RcsPresenceExchangeImplBase.java index 055fca57a628..bb034489a296 100644 --- a/telephony/java/android/telephony/ims/stub/RcsPresenceExchangeImplBase.java +++ b/telephony/java/android/telephony/ims/stub/RcsPresenceExchangeImplBase.java @@ -113,6 +113,51 @@ public class RcsPresenceExchangeImplBase extends RcsCapabilityExchange { }) public @interface PresenceResponseCode {} + + /** A capability update has been requested due to the Entity Tag (ETag) expiring. */ + public static final int CAPABILITY_UPDATE_TRIGGER_ETAG_EXPIRED = 0; + /** A capability update has been requested due to moving to LTE with VoPS disabled. */ + public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_LTE_VOPS_DISABLED = 1; + /** A capability update has been requested due to moving to LTE with VoPS enabled. */ + public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_LTE_VOPS_ENABLED = 2; + /** A capability update has been requested due to moving to eHRPD. */ + public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_EHRPD = 3; + /** A capability update has been requested due to moving to HSPA+. */ + public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_HSPAPLUS = 4; + /** A capability update has been requested due to moving to 3G. */ + public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_3G = 5; + /** A capability update has been requested due to moving to 2G. */ + public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_2G = 6; + /** A capability update has been requested due to moving to WLAN */ + public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_WLAN = 7; + /** A capability update has been requested due to moving to IWLAN */ + public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_IWLAN = 8; + /** A capability update has been requested but the reason is unknown. */ + public static final int CAPABILITY_UPDATE_TRIGGER_UNKNOWN = 9; + /** A capability update has been requested due to moving to 5G NR with VoPS disabled. */ + public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_NR5G_VOPS_DISABLED = 10; + /** A capability update has been requested due to moving to 5G NR with VoPS enabled. */ + public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_NR5G_VOPS_ENABLED = 11; + + /** @hide*/ + @IntDef(value = { + CAPABILITY_UPDATE_TRIGGER_ETAG_EXPIRED, + CAPABILITY_UPDATE_TRIGGER_MOVE_TO_LTE_VOPS_DISABLED, + CAPABILITY_UPDATE_TRIGGER_MOVE_TO_LTE_VOPS_ENABLED, + CAPABILITY_UPDATE_TRIGGER_MOVE_TO_EHRPD, + CAPABILITY_UPDATE_TRIGGER_MOVE_TO_HSPAPLUS, + CAPABILITY_UPDATE_TRIGGER_MOVE_TO_3G, + CAPABILITY_UPDATE_TRIGGER_MOVE_TO_2G, + CAPABILITY_UPDATE_TRIGGER_MOVE_TO_WLAN, + CAPABILITY_UPDATE_TRIGGER_MOVE_TO_IWLAN, + CAPABILITY_UPDATE_TRIGGER_UNKNOWN, + CAPABILITY_UPDATE_TRIGGER_MOVE_TO_NR5G_VOPS_DISABLED, + CAPABILITY_UPDATE_TRIGGER_MOVE_TO_NR5G_VOPS_ENABLED + }, prefix = "CAPABILITY_UPDATE_TRIGGER_") + @Retention(RetentionPolicy.SOURCE) + public @interface StackPublishTriggerType { + } + /** * Provide the framework with a subsequent network response update to * {@link #updateCapabilities(RcsContactUceCapability, int)} and @@ -164,15 +209,18 @@ public class RcsPresenceExchangeImplBase extends RcsCapabilityExchange { * This is typically used when trying to generate an initial PUBLISH for a new subscription to * the network. The device will cache all presence publications after boot until this method is * called once. + * @param publishTriggerType {@link StackPublishTriggerType} The reason for the capability + * update request. * @throws ImsException If this {@link RcsPresenceExchangeImplBase} instance is not currently * connected to the framework. This can happen if the {@link RcsFeature} is not * {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not received the * {@link ImsFeature#onFeatureReady()} callback. This may also happen in rare cases when the * Telephony stack has crashed. */ - public final void onNotifyUpdateCapabilites() throws ImsException { + public final void onNotifyUpdateCapabilites(@StackPublishTriggerType int publishTriggerType) + throws ImsException { try { - getListener().onNotifyUpdateCapabilities(); + getListener().onNotifyUpdateCapabilities(publishTriggerType); } catch (RemoteException e) { throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index b846a1029c68..a8e76b9d7902 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -1977,6 +1977,17 @@ interface ITelephony { */ boolean getImsProvisioningStatusForCapability(int subId, int capability, int tech); + /** + * Get the provisioning status for the IMS Rcs capability specified. + */ + boolean getRcsProvisioningStatusForCapability(int subId, int capability); + + /** + * Set the provisioning status for the IMS Rcs capability using the specified subscription. + */ + void setRcsProvisioningStatusForCapability(int subId, int capability, + boolean isProvisioned); + /** Is the capability and tech flagged as provisioned in the cache */ boolean isMmTelCapabilityProvisionedInCache(int subId, int capability, int tech); @@ -2076,6 +2087,11 @@ interface ITelephony { int getRadioHalVersion(); /** + * Get the current calling package name. + */ + String getCurrentPackageName(); + + /** * Returns true if the specified type of application (e.g. {@link #APPTYPE_CSIM} is present * on the UICC card. * @hide @@ -2126,4 +2142,9 @@ interface ITelephony { * Command line command to enable or disable handling of CEP data for test purposes. */ oneway void setCepEnabled(boolean isCepEnabled); + + /** + * Notify Rcs auto config received. + */ + void notifyRcsAutoConfigurationReceived(int subId, in byte[] config, boolean isCompressed); } diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java index 51701eb4ace5..db8c84560282 100644 --- a/telephony/java/com/android/internal/telephony/PhoneConstants.java +++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java @@ -100,9 +100,6 @@ public class PhoneConstants { public static final String DATA_APN_TYPE_KEY = "apnType"; public static final String DATA_APN_KEY = "apn"; - public static final String PHONE_IN_ECM_STATE = "phoneinECMState"; - public static final String PHONE_IN_EMERGENCY_CALL = "phoneInEmergencyCall"; - /** * Return codes for supplyPinReturnResult and * supplyPukReturnResult APIs diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index 284544b7f6f2..9ee26c28f906 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -233,11 +233,14 @@ public interface RILConstants { /** NR 5G, LTE, TD-SCDMA, CDMA, EVDO, GSM and WCDMA */ int NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA = 33; + /** Default preferred network mode */ + int DEFAULT_PREFERRED_NETWORK_MODE = NETWORK_MODE_WCDMA_PREF; + @UnsupportedAppUsage int PREFERRED_NETWORK_MODE = Optional.of(TelephonyProperties.default_network()) .filter(list -> !list.isEmpty()) .map(list -> list.get(0)) - .orElse(NETWORK_MODE_WCDMA_PREF); + .orElse(DEFAULT_PREFERRED_NETWORK_MODE); int BAND_MODE_UNSPECIFIED = 0; //"unspecified" (selected by baseband automatically) int BAND_MODE_EURO = 1; //"EURO band" (GSM-900 / DCS-1800 / WCDMA-IMT-2000) @@ -555,4 +558,5 @@ public interface RILConstants { int RIL_UNSOL_EMERGENCY_NUMBER_LIST = 1102; int RIL_UNSOL_UICC_APPLICATIONS_ENABLEMENT_CHANGED = 1103; int RIL_UNSOL_REGISTRATION_FAILED = 1104; + int RIL_UNSOL_BARRING_INFO_CHANGED = 1105; } diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java index 656628eb39d5..8cc8cf4d2a97 100644 --- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java +++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java @@ -18,10 +18,13 @@ package com.android.server; import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; + import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; @@ -36,12 +39,14 @@ import android.content.pm.VersionedPackage; import android.net.ConnectivityModuleConnector; import android.net.ConnectivityModuleConnector.ConnectivityModuleHealthListener; import android.os.Handler; +import android.os.SystemProperties; import android.os.test.TestLooper; import android.provider.DeviceConfig; import android.util.AtomicFile; import androidx.test.InstrumentationRegistry; +import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.server.PackageWatchdog.HealthCheckState; import com.android.server.PackageWatchdog.MonitoredPackage; import com.android.server.PackageWatchdog.PackageHealthObserver; @@ -54,11 +59,15 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; +import org.mockito.stubbing.Answer; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -88,6 +97,8 @@ public class PackageWatchdogTest { private PackageManager mMockPackageManager; @Captor private ArgumentCaptor<ConnectivityModuleHealthListener> mConnectivityModuleCallbackCaptor; + private MockitoSession mSession; + private HashMap<String, String> mSystemSettingsMap; @Before public void setUp() throws Exception { @@ -104,11 +115,47 @@ public class PackageWatchdogTest { res.setLongVersionCode(VERSION_CODE); return res; }); + mSession = ExtendedMockito.mockitoSession() + .initMocks(this) + .strictness(Strictness.LENIENT) + .spyStatic(SystemProperties.class) + .startMocking(); + mSystemSettingsMap = new HashMap<>(); + + + // Mock SystemProperties setter and various getters + doAnswer((Answer<Void>) invocationOnMock -> { + String key = invocationOnMock.getArgument(0); + String value = invocationOnMock.getArgument(1); + + mSystemSettingsMap.put(key, value); + return null; + } + ).when(() -> SystemProperties.set(anyString(), anyString())); + + doAnswer((Answer<Integer>) invocationOnMock -> { + String key = invocationOnMock.getArgument(0); + int defaultValue = invocationOnMock.getArgument(1); + + String storedValue = mSystemSettingsMap.get(key); + return storedValue == null ? defaultValue : Integer.parseInt(storedValue); + } + ).when(() -> SystemProperties.getInt(anyString(), anyInt())); + + doAnswer((Answer<Long>) invocationOnMock -> { + String key = invocationOnMock.getArgument(0); + long defaultValue = invocationOnMock.getArgument(1); + + String storedValue = mSystemSettingsMap.get(key); + return storedValue == null ? defaultValue : Long.parseLong(storedValue); + } + ).when(() -> SystemProperties.getLong(anyString(), anyLong())); } @After public void tearDown() throws Exception { dropShellPermissions(); + mSession.finishMocking(); } @Test @@ -968,6 +1015,54 @@ public class PackageWatchdogTest { assertThat(persistentObserver.mHealthCheckFailedPackages).isEmpty(); } + + /** Ensure that boot loop mitigation is done when the number of boots meets the threshold. */ + @Test + public void testBootLoopDetection_meetsThreshold() { + PackageWatchdog watchdog = createWatchdog(); + TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1); + watchdog.registerHealthObserver(bootObserver); + for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) { + watchdog.noteBoot(); + } + assertThat(bootObserver.mitigatedBootLoop()).isTrue(); + } + + + /** + * Ensure that boot loop mitigation is not done when the number of boots does not meet the + * threshold. + */ + @Test + public void testBootLoopDetection_doesNotMeetThreshold() { + PackageWatchdog watchdog = createWatchdog(); + TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1); + watchdog.registerHealthObserver(bootObserver); + for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT - 1; i++) { + watchdog.noteBoot(); + } + assertThat(bootObserver.mitigatedBootLoop()).isFalse(); + } + + /** + * Ensure that boot loop mitigation is done for the observer with the lowest user impact + */ + @Test + public void testBootLoopMitigationDoneForLowestUserImpact() { + PackageWatchdog watchdog = createWatchdog(); + TestObserver bootObserver1 = new TestObserver(OBSERVER_NAME_1); + bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LOW); + TestObserver bootObserver2 = new TestObserver(OBSERVER_NAME_2); + bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_MEDIUM); + watchdog.registerHealthObserver(bootObserver1); + watchdog.registerHealthObserver(bootObserver2); + for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) { + watchdog.noteBoot(); + } + assertThat(bootObserver1.mitigatedBootLoop()).isTrue(); + assertThat(bootObserver2.mitigatedBootLoop()).isFalse(); + } + private void adoptShellPermissions(String... permissions) { InstrumentationRegistry .getInstrumentation() @@ -1046,6 +1141,7 @@ public class PackageWatchdogTest { private int mLastFailureReason; private boolean mIsPersistent = false; private boolean mMayObservePackages = false; + private boolean mMitigatedBootLoop = false; final List<String> mHealthCheckFailedPackages = new ArrayList<>(); final List<String> mMitigatedPackages = new ArrayList<>(); @@ -1082,6 +1178,19 @@ public class PackageWatchdogTest { return mMayObservePackages; } + public int onBootLoop() { + return mImpact; + } + + public boolean executeBootLoopMitigation() { + mMitigatedBootLoop = true; + return true; + } + + public boolean mitigatedBootLoop() { + return mMitigatedBootLoop; + } + public int getLastFailureReason() { return mLastFailureReason; } @@ -1090,6 +1199,10 @@ public class PackageWatchdogTest { mIsPersistent = persistent; } + public void setImpact(int impact) { + mImpact = impact; + } + public void setMayObservePackages(boolean mayObservePackages) { mMayObservePackages = mayObservePackages; } diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp index 091edd4dc0d9..98e7b4e1b430 100644 --- a/tests/RollbackTest/Android.bp +++ b/tests/RollbackTest/Android.bp @@ -19,7 +19,10 @@ android_test { static_libs: ["androidx.test.rules", "cts-rollback-lib", "cts-install-lib"], test_suites: ["general-tests"], test_config: "RollbackTest.xml", - java_resources: [":com.android.apex.apkrollback.test_v2"], + java_resources: [ + ":com.android.apex.apkrollback.test_v2", + ":com.android.apex.apkrollback.test_v2Crashing" + ], } java_test_host { @@ -79,4 +82,14 @@ apex { key: "com.android.apex.apkrollback.test.key", apps: ["TestAppAv2"], installable: false, +} + +apex { + name: "com.android.apex.apkrollback.test_v2Crashing", + manifest: "testdata/manifest_v2.json", + androidManifest: "testdata/AndroidManifest.xml", + file_contexts: ":apex.test-file_contexts", + key: "com.android.apex.apkrollback.test.key", + apps: ["TestAppACrashingV2"], + installable: false, }
\ No newline at end of file diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java index f6699faf7a61..5a92d6849434 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java @@ -392,9 +392,6 @@ public class RollbackTest { RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS, Long.toString(expirationTime), false /* makeDefault*/); - // Pull the new expiration time from DeviceConfig - rm.reloadPersistedData(); - // Uninstall TestApp.A Uninstall.packages(TestApp.A); assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1); @@ -457,9 +454,6 @@ public class RollbackTest { RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS, Long.toString(expirationTime), false /* makeDefault*/); - // Pull the new expiration time from DeviceConfig - rm.reloadPersistedData(); - // Install app A with rollback enabled Uninstall.packages(TestApp.A); Install.single(TestApp.A1).commit(); 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 3877cc139a3e..80491cd98bc9 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java @@ -23,12 +23,14 @@ import static com.google.common.truth.Truth.assertThat; import android.Manifest; import android.content.ComponentName; +import android.content.Context; import android.content.Intent; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.rollback.RollbackInfo; import android.content.rollback.RollbackManager; import android.os.ParcelFileDescriptor; +import android.os.storage.StorageManager; import android.provider.DeviceConfig; import androidx.test.platform.app.InstrumentationRegistry; @@ -185,12 +187,6 @@ public class StagedRollbackTest { */ @Test public void testNativeWatchdogTriggersRollback_Phase1() throws Exception { - // When multiple staged sessions are installed on a device which doesn't support checkpoint, - // only the 1st one will prevail. We have to check no other rollbacks available to ensure - // TestApp.A is always the 1st and the only one to commit so rollback can work as intended. - // If there are leftover rollbacks from previous tests, this assertion will fail. - assertThat(RollbackUtils.getRollbackManager().getAvailableRollbacks()).isEmpty(); - Uninstall.packages(TestApp.A); Install.single(TestApp.A1).commit(); assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); @@ -220,6 +216,64 @@ public class StagedRollbackTest { TestApp.A)).isNotNull(); } + /** + * Stage install an apk with rollback that will be later triggered by unattributable crash. + */ + @Test + public void testNativeWatchdogTriggersRollbackForAll_Phase1() throws Exception { + Uninstall.packages(TestApp.A); + Install.single(TestApp.A1).commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); + + Install.single(TestApp.A2).setEnableRollback().setStaged().commit(); + } + + /** + * Verify the rollback is available and then install another package with rollback. + */ + @Test + public void testNativeWatchdogTriggersRollbackForAll_Phase2() throws Exception { + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); + RollbackManager rm = RollbackUtils.getRollbackManager(); + assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), + TestApp.A)).isNotNull(); + + // Install another package with rollback + Uninstall.packages(TestApp.B); + Install.single(TestApp.B1).commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1); + + Install.single(TestApp.B2).setEnableRollback().setStaged().commit(); + } + + /** + * Verify the rollbacks are available. + */ + @Test + public void testNativeWatchdogTriggersRollbackForAll_Phase3() throws Exception { + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); + assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); + RollbackManager rm = RollbackUtils.getRollbackManager(); + assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), + TestApp.A)).isNotNull(); + assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), + TestApp.B)).isNotNull(); + } + + /** + * Verify the rollbacks are committed after crashing. + */ + @Test + public void testNativeWatchdogTriggersRollbackForAll_Phase4() throws Exception { + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); + assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1); + RollbackManager rm = RollbackUtils.getRollbackManager(); + assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), + TestApp.A)).isNotNull(); + assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), + TestApp.B)).isNotNull(); + } + @Test public void testNetworkFailedRollback_Phase1() throws Exception { // Remove available rollbacks and uninstall NetworkStack on /data/ @@ -438,6 +492,7 @@ public class StagedRollbackTest { RollbackManager rm = RollbackUtils.getRollbackManager(); rm.getAvailableRollbacks().stream().flatMap(info -> info.getPackages().stream()) .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage); + assertThat(RollbackUtils.getRollbackManager().getAvailableRollbacks()).isEmpty(); } private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test"; @@ -445,8 +500,9 @@ public class StagedRollbackTest { APK_IN_APEX_TESTAPEX_NAME, 1, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v1.apex"); private static final TestApp TEST_APEX_WITH_APK_V2 = new TestApp("TestApexWithApkV2", APK_IN_APEX_TESTAPEX_NAME, 2, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v2.apex"); - private static final TestApp TEST_APP_A_V2_UNKNOWN = new TestApp("Av2Unknown", TestApp.A, 0, - /*isApex*/false, "TestAppAv2.apk"); + private static final TestApp TEST_APEX_WITH_APK_V2_CRASHING = new TestApp( + "TestApexWithApkV2Crashing", APK_IN_APEX_TESTAPEX_NAME, 2, /*isApex*/true, + APK_IN_APEX_TESTAPEX_NAME + "_v2Crashing.apex"); @Test public void testRollbackApexWithApk_Phase1() throws Exception { @@ -468,7 +524,7 @@ public class StagedRollbackTest { assertThat(available).isStaged(); assertThat(available).packagesContainsExactly( Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1), - Rollback.from(TEST_APP_A_V2_UNKNOWN).to(TestApp.A1)); + Rollback.from(TestApp.A, 0).to(TestApp.A1)); RollbackUtils.rollback(available.getRollbackId(), TEST_APEX_WITH_APK_V2); RollbackInfo committed = RollbackUtils.getCommittedRollbackById(available.getRollbackId()); @@ -476,7 +532,7 @@ public class StagedRollbackTest { assertThat(committed).isStaged(); assertThat(committed).packagesContainsExactly( Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1), - Rollback.from(TEST_APP_A_V2_UNKNOWN).to(TestApp.A1)); + Rollback.from(TestApp.A, 0).to(TestApp.A1)); assertThat(committed).causePackagesContainsExactly(TEST_APEX_WITH_APK_V2); assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1); @@ -493,9 +549,51 @@ public class StagedRollbackTest { InstallUtils.processUserData(TestApp.A); } + /** + * Installs an apex with an apk that can crash. + */ + @Test + public void testRollbackApexWithApkCrashing_Phase1() throws Exception { + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); + int sessionId = Install.single(TEST_APEX_WITH_APK_V2_CRASHING).setStaged() + .setEnableRollback().commit(); + InstallUtils.waitForSessionReady(sessionId); + } + + /** + * Verifies rollback has been enabled successfully. Then makes TestApp.A crash. + */ + @Test + public void testRollbackApexWithApkCrashing_Phase2() throws Exception { + assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(2); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); + + RollbackInfo available = RollbackUtils.getAvailableRollback(APK_IN_APEX_TESTAPEX_NAME); + assertThat(available).isStaged(); + assertThat(available).packagesContainsExactly( + Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1), + Rollback.from(TestApp.A, 0).to(TestApp.A1)); + + // Crash TestApp.A PackageWatchdog#TRIGGER_FAILURE_COUNT times to trigger rollback + RollbackUtils.sendCrashBroadcast(TestApp.A, 5); + } + + @Test + public void testRollbackApexWithApkCrashing_Phase3() throws Exception { + assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(1); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); + } + private static void runShellCommand(String cmd) { ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation().getUiAutomation() .executeShellCommand(cmd); IoUtils.closeQuietly(pfd); } + + @Test + public void isCheckpointSupported() { + Context context = InstrumentationRegistry.getInstrumentation().getContext(); + StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); + assertThat(sm.isCheckpointSupported()).isTrue(); + } } 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 6daa6bc723c4..672cbb084dae 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 @@ -17,6 +17,7 @@ package com.android.tests.rollback.host; import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; import static org.testng.Assert.assertThrows; import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; @@ -62,6 +63,7 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { "rm -f /system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex " + "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex"); getDevice().reboot(); + runPhase("testCleanUp"); } @After @@ -95,7 +97,6 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { @Test public void testNativeWatchdogTriggersRollback() throws Exception { - //Stage install ModuleMetadata package - this simulates a Mainline module update runPhase("testNativeWatchdogTriggersRollback_Phase1"); // Reboot device to activate staged package @@ -121,6 +122,40 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { runPhase("testNativeWatchdogTriggersRollback_Phase3"); } + @Test + public void testNativeWatchdogTriggersRollbackForAll() throws Exception { + // This test requires committing multiple staged rollbacks + assumeTrue(isCheckpointSupported()); + + // Install a package with rollback enabled. + runPhase("testNativeWatchdogTriggersRollbackForAll_Phase1"); + getDevice().reboot(); + + // Once previous staged install is applied, install another package + runPhase("testNativeWatchdogTriggersRollbackForAll_Phase2"); + getDevice().reboot(); + + // Verify the new staged install has also been applied successfully. + runPhase("testNativeWatchdogTriggersRollbackForAll_Phase3"); + + // crash system_server enough times to trigger a rollback + crashProcess("system_server", NATIVE_CRASHES_THRESHOLD); + + // Rollback should be committed automatically now. + // Give time for rollback to be committed. This could take a while, + // because we need all of the following to happen: + // 1. system_server comes back up and boot completes. + // 2. Rollback health observer detects updatable crashing signal. + // 3. Staged rollback session becomes ready. + // 4. Device actually reboots. + // So we give a generous timeout here. + assertTrue(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(5))); + getDevice().waitForDeviceAvailable(); + + // verify all available rollbacks have been committed + runPhase("testNativeWatchdogTriggersRollbackForAll_Phase4"); + } + /** * Tests failed network health check triggers watchdog staged rollbacks. */ @@ -226,6 +261,34 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { runPhase("testRollbackApexWithApk_Phase3"); } + /** + * Tests that RollbackPackageHealthObserver is observing apk-in-apex. + */ + @Test + public void testRollbackApexWithApkCrashing() throws Exception { + getDevice().uninstallPackage("com.android.cts.install.lib.testapp.A"); + CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild()); + final String fileName = APK_IN_APEX_TESTAPEX_NAME + "_v1.apex"; + final File apex = buildHelper.getTestFile(fileName); + if (!getDevice().isAdbRoot()) { + getDevice().enableAdbRoot(); + } + getDevice().remountSystemWritable(); + assertTrue(getDevice().pushFile(apex, "/system/apex/" + fileName)); + getDevice().reboot(); + + // Install an apex with apk that crashes + runPhase("testRollbackApexWithApkCrashing_Phase1"); + getDevice().reboot(); + // Verify apex was installed and then crash the apk + runPhase("testRollbackApexWithApkCrashing_Phase2"); + // Wait for crash to trigger rollback + assertTrue(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(5))); + getDevice().waitForDeviceAvailable(); + // Verify rollback occurred due to crash of apk-in-apex + runPhase("testRollbackApexWithApkCrashing_Phase3"); + } + private void crashProcess(String processName, int numberOfCrashes) throws Exception { String pid = ""; String lastPid = "invalid"; @@ -244,4 +307,13 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { // Find the NetworkStack path (can be NetworkStack.apk or NetworkStackNext.apk) return getDevice().executeShellCommand("ls /system/priv-app/NetworkStack*/*.apk"); } + + private boolean isCheckpointSupported() throws Exception { + try { + runPhase("isCheckpointSupported"); + return true; + } catch (AssertionError ignore) { + return false; + } + } } diff --git a/tests/WindowInsetsTests/Android.bp b/tests/WindowInsetsTests/Android.bp new file mode 100644 index 000000000000..12395e70468d --- /dev/null +++ b/tests/WindowInsetsTests/Android.bp @@ -0,0 +1,22 @@ +// 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. + +android_test { + name: "WindowInsetsTests", + srcs: ["src/**/*.java"], + resource_dirs: ["res"], + certificate: "platform", + platform_apis: true, +} + diff --git a/tests/WindowInsetsTests/AndroidManifest.xml b/tests/WindowInsetsTests/AndroidManifest.xml new file mode 100644 index 000000000000..8d33f70c33a2 --- /dev/null +++ b/tests/WindowInsetsTests/AndroidManifest.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (018C) 2018 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.google.android.test.windowinsetstests"> + + <application android:label="@string/activity_title"> + <activity android:name=".WindowInsetsActivity" + android:theme="@android:style/Theme.Material" + android:windowSoftInputMode="adjustResize"> + + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/tests/WindowInsetsTests/res/layout/window_inset_activity.xml b/tests/WindowInsetsTests/res/layout/window_inset_activity.xml new file mode 100644 index 000000000000..38e00298f49f --- /dev/null +++ b/tests/WindowInsetsTests/res/layout/window_inset_activity.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2018 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center" + android:id="@+id/root"> + + <Button + android:id="@+id/button" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:text="Hello insets" /> + +</LinearLayout> + diff --git a/tests/WindowInsetsTests/res/values/strings.xml b/tests/WindowInsetsTests/res/values/strings.xml new file mode 100644 index 000000000000..242823d06fc8 --- /dev/null +++ b/tests/WindowInsetsTests/res/values/strings.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2018 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="activity_title">Window Insets Tests</string> +</resources> diff --git a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java new file mode 100644 index 000000000000..b8b2de5141a7 --- /dev/null +++ b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2018 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.android.test.windowinsetstests; + +import android.animation.ObjectAnimator; +import android.animation.TypeEvaluator; +import android.animation.ValueAnimator; +import android.app.Activity; +import android.graphics.Insets; +import android.os.Bundle; +import android.util.Property; +import android.view.View; +import android.view.WindowInsets; +import android.view.WindowInsets.Type; +import android.view.WindowInsetsAnimationCallback; +import android.view.WindowInsetsAnimationCallback.InsetsAnimation; +import android.view.WindowInsetsAnimationControlListener; +import android.view.WindowInsetsAnimationController; + +import com.google.android.test.windowinsetstests.R; + +public class WindowInsetsActivity extends Activity { + + private View mRoot; + private View mButton; + + private static class InsetsProperty extends Property<WindowInsetsAnimationController, Insets> { + + private final View mViewToAnimate; + private final Insets mShowingInsets; + + public InsetsProperty(View viewToAnimate, Insets showingInsets) { + super(Insets.class, "Insets"); + mViewToAnimate = viewToAnimate; + mShowingInsets = showingInsets; + } + + @Override + public Insets get(WindowInsetsAnimationController object) { + return object.getCurrentInsets(); + } + + @Override + public void set(WindowInsetsAnimationController object, Insets value) { + object.setInsetsAndAlpha(value, 1.0f, 0.5f); + if (mShowingInsets.bottom != 0) { + mViewToAnimate.setTranslationY(mShowingInsets.bottom - value.bottom); + } else if (mShowingInsets.right != 0) { + mViewToAnimate.setTranslationX(mShowingInsets.right - value.right); + } else if (mShowingInsets.left != 0) { + mViewToAnimate.setTranslationX(value.left - mShowingInsets.left); + } + } + }; + + float showY; + float hideY; + InsetsAnimation imeAnim; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.window_inset_activity); + mRoot = findViewById(R.id.root); + mButton = findViewById(R.id.button); + mButton.setOnClickListener(v -> { + if (!v.getRootWindowInsets().isVisible(Type.ime())) { + v.getWindowInsetsController().show(Type.ime()); + } else { + v.getWindowInsetsController().hide(Type.ime()); + } + }); + mRoot.getViewTreeObserver().addOnGlobalLayoutListener(() -> { + if (imeAnim == null) { + return; + } + if (mRoot.getRootWindowInsets().isVisible(Type.ime())) { + showY = mButton.getTop(); + } else { + hideY = mButton.getTop(); + } + }); + mRoot.setWindowInsetsAnimationCallback(new WindowInsetsAnimationCallback() { + + @Override + public void onPrepare(InsetsAnimation animation) { + if ((animation.getTypeMask() & Type.ime()) != 0) { + imeAnim = animation; + } + if (mRoot.getRootWindowInsets().isVisible(Type.ime())) { + showY = mButton.getTop(); + } else { + hideY = mButton.getTop(); + } + } + + @Override + public WindowInsets onProgress(WindowInsets insets) { + mButton.setY(hideY + (showY - hideY) * imeAnim.getInterpolatedFraction()); + return insets; + } + + @Override + public AnimationBounds onStart(InsetsAnimation animation, + AnimationBounds bounds) { + return bounds; + } + + @Override + public void onFinish(InsetsAnimation animation) { + imeAnim = null; + } + }); + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + + TypeEvaluator<Insets> evaluator = (fraction, startValue, endValue) -> Insets.of( + (int)(startValue.left + fraction * (endValue.left - startValue.left)), + (int)(startValue.top + fraction * (endValue.top - startValue.top)), + (int)(startValue.right + fraction * (endValue.right - startValue.right)), + (int)(startValue.bottom + fraction * (endValue.bottom - startValue.bottom))); + + WindowInsetsAnimationControlListener listener = new WindowInsetsAnimationControlListener() { + @Override + public void onReady(WindowInsetsAnimationController controller, int types) { + ObjectAnimator animator = ObjectAnimator.ofObject(controller, + new InsetsProperty(findViewById(R.id.button), + controller.getShownStateInsets()), + evaluator, controller.getShownStateInsets(), + controller.getHiddenStateInsets()); + animator.setRepeatCount(ValueAnimator.INFINITE); + animator.setRepeatMode(ValueAnimator.REVERSE); + animator.start(); + } + + @Override + public void onCancelled() { + + } + }; + } +} diff --git a/tests/net/common/java/android/net/CaptivePortalTest.java b/tests/net/common/java/android/net/CaptivePortalTest.java index eed7159ffddc..ca4ba63142a2 100644 --- a/tests/net/common/java/android/net/CaptivePortalTest.java +++ b/tests/net/common/java/android/net/CaptivePortalTest.java @@ -44,6 +44,11 @@ public class CaptivePortalTest { } @Override + public void appRequest(final int request) throws RemoteException { + mCode = request; + } + + @Override public void logEvent(int eventId, String packageName) throws RemoteException { mCode = eventId; mPackageName = packageName; @@ -80,6 +85,12 @@ public class CaptivePortalTest { } @Test + public void testReevaluateNetwork() { + final MyCaptivePortalImpl result = runCaptivePortalTest(c -> c.reevaluateNetwork()); + assertEquals(result.mCode, CaptivePortal.APP_REQUEST_REEVALUATION_REQUIRED); + } + + @Test public void testLogEvent() { final MyCaptivePortalImpl result = runCaptivePortalTest(c -> c.logEvent( MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_ACTIVITY, diff --git a/tests/net/common/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java index a7328acb73b5..6005cc375d5c 100644 --- a/tests/net/common/java/android/net/LinkPropertiesTest.java +++ b/tests/net/common/java/android/net/LinkPropertiesTest.java @@ -27,8 +27,8 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import android.net.LinkProperties.CompareResult; import android.net.LinkProperties.ProvisioningChange; +import android.net.util.LinkPropertiesUtils.CompareResult; import android.system.OsConstants; import android.util.ArraySet; diff --git a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java index 0ab5c97d317e..11d5b250d752 100644 --- a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java +++ b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java @@ -222,7 +222,7 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork { @Override public Network getNetwork() { - return new Network(mNetworkAgent.netId); + return mNetworkAgent.network; } public void expectPreventReconnectReceived(long timeoutMs) { diff --git a/tests/net/java/android/net/MacAddressTest.java b/tests/net/java/android/net/MacAddressTest.java index daf187d01533..91c9a2a38036 100644 --- a/tests/net/java/android/net/MacAddressTest.java +++ b/tests/net/java/android/net/MacAddressTest.java @@ -22,6 +22,8 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import android.net.util.MacAddressUtils; + import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -122,11 +124,11 @@ public class MacAddressTest { for (MacAddress mac : multicastAddresses) { String msg = mac.toString() + " expected to be a multicast address"; - assertTrue(msg, mac.isMulticastAddress()); + assertTrue(msg, MacAddressUtils.isMulticastAddress(mac)); } for (MacAddress mac : unicastAddresses) { String msg = mac.toString() + " expected not to be a multicast address"; - assertFalse(msg, mac.isMulticastAddress()); + assertFalse(msg, MacAddressUtils.isMulticastAddress(mac)); } } @@ -156,7 +158,7 @@ public class MacAddressTest { public void testMacAddressConversions() { final int iterations = 10000; for (int i = 0; i < iterations; i++) { - MacAddress mac = MacAddress.createRandomUnicastAddress(); + MacAddress mac = MacAddressUtils.createRandomUnicastAddress(); String stringRepr = mac.toString(); byte[] bytesRepr = mac.toByteArray(); @@ -188,7 +190,7 @@ public class MacAddressTest { final String expectedLocalOui = "26:5f:78"; final MacAddress base = MacAddress.fromString(anotherOui + ":0:0:0"); for (int i = 0; i < iterations; i++) { - MacAddress mac = MacAddress.createRandomUnicastAddress(base, r); + MacAddress mac = MacAddressUtils.createRandomUnicastAddress(base, r); String stringRepr = mac.toString(); assertTrue(stringRepr + " expected to be a locally assigned address", @@ -199,7 +201,7 @@ public class MacAddressTest { } for (int i = 0; i < iterations; i++) { - MacAddress mac = MacAddress.createRandomUnicastAddress(); + MacAddress mac = MacAddressUtils.createRandomUnicastAddress(); String stringRepr = mac.toString(); assertTrue(stringRepr + " expected to be a locally assigned address", diff --git a/tests/net/java/android/net/TelephonyNetworkSpecifierTest.java b/tests/net/java/android/net/TelephonyNetworkSpecifierTest.java new file mode 100644 index 000000000000..47afed441ace --- /dev/null +++ b/tests/net/java/android/net/TelephonyNetworkSpecifierTest.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2018 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 static com.android.testutils.ParcelUtilsKt.assertParcelSane; + +import static org.junit.Assert.assertEquals; + +import android.telephony.SubscriptionManager; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; + +/** + * Unit test for {@link android.net.TelephonyNetworkSpecifier}. + */ +@SmallTest +public class TelephonyNetworkSpecifierTest { + private static final int TEST_SUBID = 5; + + /** + * Validate that IllegalArgumentException will be thrown if build TelephonyNetworkSpecifier + * without calling {@link TelephonyNetworkSpecifier.Builder#setSubscriptionId(int)}. + */ + @Test + public void testBuilderBuildWithDefault() { + try { + new TelephonyNetworkSpecifier.Builder().build(); + } catch (IllegalArgumentException iae) { + // expected, test pass + } + } + + /** + * Validate that no exception will be thrown even if pass invalid subscription id to + * {@link TelephonyNetworkSpecifier.Builder#setSubscriptionId(int)}. + */ + @Test + public void testBuilderBuildWithInvalidSubId() { + TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier.Builder() + .setSubscriptionId(SubscriptionManager.INVALID_SUBSCRIPTION_ID) + .build(); + assertEquals(specifier.getSubscriptionId(), SubscriptionManager.INVALID_SUBSCRIPTION_ID); + } + + /** + * Validate the correctness of TelephonyNetworkSpecifier when provide valid subId. + */ + @Test + public void testBuilderBuildWithValidSubId() { + final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier.Builder() + .setSubscriptionId(TEST_SUBID) + .build(); + assertEquals(TEST_SUBID, specifier.getSubscriptionId()); + } + + /** + * Validate that parcel marshalling/unmarshalling works. + */ + @Test + public void testParcel() { + TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier.Builder() + .setSubscriptionId(TEST_SUBID) + .build(); + assertParcelSane(specifier, 1 /* fieldCount */); + } +} diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index b2d363e27839..1901a1db633b 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -575,7 +575,7 @@ public class ConnectivityServiceTest { } }; - assertEquals(na.netId, nmNetworkCaptor.getValue().netId); + assertEquals(na.network.netId, nmNetworkCaptor.getValue().netId); mNmCallbacks = nmCbCaptor.getValue(); mNmCallbacks.onNetworkMonitorCreated(mNetworkMonitor); diff --git a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java index 9e915aec6832..e863266c4b49 100644 --- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java @@ -35,7 +35,6 @@ import android.net.ConnectivityManager; import android.net.IDnsResolver; import android.net.INetd; import android.net.Network; -import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkProvider; @@ -75,7 +74,6 @@ public class LingerMonitorTest { @Mock INetd mNetd; @Mock INetworkManagementService mNMS; @Mock Context mCtx; - @Mock NetworkAgentConfig mAgentConfig; @Mock NetworkNotificationManager mNotifier; @Mock Resources mResources; @@ -358,7 +356,7 @@ public class LingerMonitorTest { NetworkScore ns = new NetworkScore(); ns.putIntExtension(NetworkScore.LEGACY_SCORE, 50); NetworkAgentInfo nai = new NetworkAgentInfo(null, null, new Network(netId), info, null, - caps, ns, mCtx, null, mAgentConfig, mConnService, mNetd, mDnsResolver, mNMS, + caps, ns, mCtx, null, null /* config */, mConnService, mNetd, mDnsResolver, mNMS, NetworkProvider.ID_NONE); nai.everValidated = true; return nai; diff --git a/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java b/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java index b783467cfaf2..de1028cd2303 100644 --- a/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java +++ b/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java @@ -51,6 +51,7 @@ import android.net.NetworkPolicy; import android.net.NetworkPolicyManager; import android.net.NetworkTemplate; import android.net.StringNetworkSpecifier; +import android.net.TelephonyNetworkSpecifier; import android.os.Handler; import android.provider.Settings; import android.telephony.TelephonyManager; @@ -229,7 +230,7 @@ public class MultipathPolicyTrackerTest { verify(mCM).registerNetworkCallback(any(), networkCallback.capture(), any()); // Simulate callback after capability changes - final NetworkCapabilities capabilities = new NetworkCapabilities() + NetworkCapabilities capabilities = new NetworkCapabilities() .addCapability(NET_CAPABILITY_INTERNET) .addTransportType(TRANSPORT_CELLULAR) .setNetworkSpecifier(new StringNetworkSpecifier("234")); @@ -239,6 +240,19 @@ public class MultipathPolicyTrackerTest { networkCallback.getValue().onCapabilitiesChanged( TEST_NETWORK, capabilities); + + // make sure it also works with the new introduced TelephonyNetworkSpecifier + capabilities = new NetworkCapabilities() + .addCapability(NET_CAPABILITY_INTERNET) + .addTransportType(TRANSPORT_CELLULAR) + .setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder() + .setSubscriptionId(234).build()); + if (!roaming) { + capabilities.addCapability(NET_CAPABILITY_NOT_ROAMING); + } + networkCallback.getValue().onCapabilitiesChanged( + TEST_NETWORK, + capabilities); } @Test diff --git a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java index cf70f5d499d1..9b248878fe96 100644 --- a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java +++ b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java @@ -63,7 +63,6 @@ public class Nat464XlatTest { static final int NETID = 42; @Mock ConnectivityService mConnectivity; - @Mock NetworkAgentConfig mAgentConfig; @Mock IDnsResolver mDnsResolver; @Mock INetd mNetd; @Mock INetworkManagementService mNms; @@ -72,6 +71,7 @@ public class Nat464XlatTest { TestLooper mLooper; Handler mHandler; + NetworkAgentConfig mAgentConfig = new NetworkAgentConfig(); Nat464Xlat makeNat464Xlat() { return new Nat464Xlat(mNai, mNetd, mDnsResolver, mNms) { diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java index 8d99ac7100eb..8eac3ea13a23 100644 --- a/tests/testables/src/android/testing/TestableLooper.java +++ b/tests/testables/src/android/testing/TestableLooper.java @@ -234,6 +234,7 @@ public class TestableLooper { try { mLooper = setAsMain ? Looper.getMainLooper() : createLooper(); mTestableLooper = new TestableLooper(mLooper, false); + mTestableLooper.getLooper().getThread().setName(test.getClass().getName()); } catch (Exception e) { throw new RuntimeException(e); } diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp index 3623b1112bc6..469128b1e50b 100644 --- a/tools/aapt2/ResourceUtils.cpp +++ b/tools/aapt2/ResourceUtils.cpp @@ -800,7 +800,12 @@ std::unique_ptr<Item> ParseBinaryResValue(const ResourceType& type, const Config } // This is a normal reference. - return util::make_unique<Reference>(data, ref_type); + auto reference = util::make_unique<Reference>(data, ref_type); + if (res_value.dataType == android::Res_value::TYPE_DYNAMIC_REFERENCE || + res_value.dataType == android::Res_value::TYPE_DYNAMIC_ATTRIBUTE) { + reference->is_dynamic = true; + } + return reference; } break; } diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp index c016cb44af00..b08bf9a1ff17 100644 --- a/tools/aapt2/ResourceUtils_test.cpp +++ b/tools/aapt2/ResourceUtils_test.cpp @@ -109,6 +109,20 @@ TEST(ResourceUtilsTest, ParsePrivateReference) { EXPECT_TRUE(private_ref); } +TEST(ResourceUtilsTest, ParseBinaryDynamicReference) { + android::Res_value value = {}; + value.data = util::HostToDevice32(0x01); + value.dataType = android::Res_value::TYPE_DYNAMIC_REFERENCE; + std::unique_ptr<Item> item = ResourceUtils::ParseBinaryResValue(ResourceType::kId, + android::ConfigDescription(), + android::ResStringPool(), value, + nullptr); + + Reference* ref = ValueCast<Reference>(item.get()); + EXPECT_TRUE(ref->is_dynamic); + EXPECT_EQ(ref->id.value().id, 0x01); +} + TEST(ResourceUtilsTest, FailToParseAutoCreateNonIdReference) { bool create = false; bool private_ref = false; diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto index 7498e132d943..8a2f5afa7255 100644 --- a/tools/aapt2/Resources.proto +++ b/tools/aapt2/Resources.proto @@ -269,6 +269,11 @@ message CompoundValue { } } +// Message holding a boolean, so it can be optionally encoded. +message Boolean { + bool value = 1; +} + // A value that is a reference to another resource. This reference can be by name or resource ID. message Reference { enum Type { @@ -289,6 +294,9 @@ message Reference { // Whether this reference is referencing a private resource (@*package:type/entry). bool private = 4; + + // Whether this reference is dynamic. + Boolean is_dynamic = 5; } // A value that represents an ID. This is just a placeholder, as ID values are used to occupy a diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp index 58e232c33985..cbce8a59bae3 100644 --- a/tools/aapt2/format/binary/TableFlattener.cpp +++ b/tools/aapt2/format/binary/TableFlattener.cpp @@ -59,10 +59,22 @@ static void strcpy16_htod(uint16_t* dst, size_t len, const StringPiece16& src) { dst[i] = 0; } +static bool cmp_style_ids(ResourceId a, ResourceId b) { + // If one of a and b is from the framework package (package ID 0x01), and the + // other is a dynamic ID (package ID 0x00), then put the dynamic ID after the + // framework ID. This ensures that when AssetManager resolves the dynamic IDs, + // they will be in sorted order as expected by AssetManager. + if ((a.package_id() == kFrameworkPackageId && b.package_id() == 0x00) || + (a.package_id() == 0x00 && b.package_id() == kFrameworkPackageId)) { + return b < a; + } + return a < b; +} + static bool cmp_style_entries(const Style::Entry& a, const Style::Entry& b) { if (a.key.id) { if (b.key.id) { - return a.key.id.value() < b.key.id.value(); + return cmp_style_ids(a.key.id.value(), b.key.id.value()); } return true; } else if (!b.key.id) { diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp index 8fbdd7f27041..af2293f0f82b 100644 --- a/tools/aapt2/format/binary/TableFlattener_test.cpp +++ b/tools/aapt2/format/binary/TableFlattener_test.cpp @@ -431,6 +431,47 @@ TEST_F(TableFlattenerTest, FlattenSharedLibrary) { EXPECT_EQ("lib", iter->second); } +TEST_F(TableFlattenerTest, FlattenSharedLibraryWithStyle) { + std::unique_ptr<IAaptContext> context = + test::ContextBuilder().SetCompilationPackage("lib").SetPackageId(0x00).Build(); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("lib", 0x00) + .AddValue("lib:style/Theme", + ResourceId(0x00030001), + test::StyleBuilder() + .AddItem("lib:attr/bar", ResourceId(0x00010002), + ResourceUtils::TryParseInt("2")) + .AddItem("lib:attr/foo", ResourceId(0x00010001), + ResourceUtils::TryParseInt("1")) + .AddItem("android:attr/bar", ResourceId(0x01010002), + ResourceUtils::TryParseInt("4")) + .AddItem("android:attr/foo", ResourceId(0x01010001), + ResourceUtils::TryParseInt("3")) + .Build()) + .Build(); + ResourceTable result; + ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result)); + + Maybe<ResourceTable::SearchResult> search_result = + result.FindResource(test::ParseNameOrDie("lib:style/Theme")); + ASSERT_TRUE(search_result); + EXPECT_EQ(0x00u, search_result.value().package->id.value()); + EXPECT_EQ(0x03u, search_result.value().type->id.value()); + EXPECT_EQ(0x01u, search_result.value().entry->id.value()); + ASSERT_EQ(1u, search_result.value().entry->values.size()); + Value* value = search_result.value().entry->values[0]->value.get(); + Style* style = ValueCast<Style>(value); + ASSERT_TRUE(style); + ASSERT_EQ(4u, style->entries.size()); + // Ensure the attributes from the shared library come after the items from + // android. + EXPECT_EQ(0x01010001, style->entries[0].key.id.value()); + EXPECT_EQ(0x01010002, style->entries[1].key.id.value()); + EXPECT_EQ(0x00010001, style->entries[2].key.id.value()); + EXPECT_EQ(0x00010002, style->entries[3].key.id.value()); +} + TEST_F(TableFlattenerTest, FlattenTableReferencingSharedLibraries) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().SetCompilationPackage("app").SetPackageId(0x7f).Build(); diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp index efbf636878f2..4cd6e930915d 100644 --- a/tools/aapt2/format/proto/ProtoDeserialize.cpp +++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp @@ -634,6 +634,7 @@ static bool DeserializeReferenceFromPb(const pb::Reference& pb_ref, Reference* o std::string* out_error) { out_ref->reference_type = DeserializeReferenceTypeFromPb(pb_ref.type()); out_ref->private_reference = pb_ref.private_(); + out_ref->is_dynamic = pb_ref.is_dynamic().value(); if (pb_ref.id() != 0) { out_ref->id = ResourceId(pb_ref.id()); diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp index e4b3fce52166..d9f6c193fc2f 100644 --- a/tools/aapt2/format/proto/ProtoSerialize.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize.cpp @@ -418,6 +418,9 @@ static void SerializeReferenceToPb(const Reference& ref, pb::Reference* pb_ref) pb_ref->set_private_(ref.private_reference); pb_ref->set_type(SerializeReferenceTypeToPb(ref.reference_type)); + if (ref.is_dynamic) { + pb_ref->mutable_is_dynamic()->set_value(ref.is_dynamic); + } } template <typename T> diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp index e7f23302652c..61a8335e17a7 100644 --- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp @@ -608,4 +608,41 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) { ASSERT_FALSE(search_result.value().entry->overlayable_item); } +TEST(ProtoSerializeTest, SerializeAndDeserializeDynamicReference) { + Reference ref(ResourceId(0x00010001)); + ref.is_dynamic = true; + + pb::Item pb_item; + SerializeItemToPb(ref, &pb_item); + + ASSERT_TRUE(pb_item.has_ref()); + EXPECT_EQ(pb_item.ref().id(), ref.id.value().id); + EXPECT_TRUE(pb_item.ref().is_dynamic().value()); + + std::unique_ptr<Item> item = DeserializeItemFromPb(pb_item, android::ResStringPool(), + android::ConfigDescription(), nullptr, + nullptr, nullptr); + Reference* actual_ref = ValueCast<Reference>(item.get()); + EXPECT_EQ(actual_ref->id.value().id, ref.id.value().id); + EXPECT_TRUE(actual_ref->is_dynamic); +} + +TEST(ProtoSerializeTest, SerializeAndDeserializeNonDynamicReference) { + Reference ref(ResourceId(0x00010001)); + + pb::Item pb_item; + SerializeItemToPb(ref, &pb_item); + + ASSERT_TRUE(pb_item.has_ref()); + EXPECT_EQ(pb_item.ref().id(), ref.id.value().id); + EXPECT_FALSE(pb_item.ref().has_is_dynamic()); + + std::unique_ptr<Item> item = DeserializeItemFromPb(pb_item, android::ResStringPool(), + android::ConfigDescription(), nullptr, + nullptr, nullptr); + Reference* actual_ref = ValueCast<Reference>(item.get()); + EXPECT_EQ(actual_ref->id.value().id, ref.id.value().id); + EXPECT_FALSE(actual_ref->is_dynamic); +} + } // namespace aapt diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp index 27960c8ed4db..954d4010d181 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -349,6 +349,7 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, } return true; }); + manifest_action["uses-sdk"]["extension-sdk"]; // Instrumentation actions. manifest_action["instrumentation"].Action(RequiredNameIsJavaClassName); diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt index 99a26dc80288..3c55237ce443 100644 --- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt +++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt @@ -53,36 +53,38 @@ object ProtoLogTool { val executor = newThreadPool() - command.javaSourceArgs.map { path -> - executor.submitCallable { - val transformer = SourceTransformer(command.protoLogImplClassNameArg, - command.protoLogCacheClassNameArg, processor) - val file = File(path) - val text = injector.readText(file) - val outSrc = try { - val code = tryParse(text, path) - if (containsProtoLogText(text, command.protoLogClassNameArg)) { - transformer.processClass(text, path, packagePath(file, code), code) - } else { + try { + command.javaSourceArgs.map { path -> + executor.submitCallable { + val transformer = SourceTransformer(command.protoLogImplClassNameArg, + command.protoLogCacheClassNameArg, processor) + val file = File(path) + val text = injector.readText(file) + val outSrc = try { + val code = tryParse(text, path) + if (containsProtoLogText(text, command.protoLogClassNameArg)) { + transformer.processClass(text, path, packagePath(file, code), code) + } else { + text + } + } catch (ex: ParsingException) { + // If we cannot parse this file, skip it (and log why). Compilation will + // fail in a subsequent build step. + injector.reportParseError(ex) text } - } catch (ex: ParsingException) { - // If we cannot parse this file, skip it (and log why). Compilation will fail - // in a subsequent build step. - injector.reportParseError(ex) - text + path to outSrc } - path to outSrc + }.map { future -> + val (path, outSrc) = future.get() + outJar.putNextEntry(ZipEntry(path)) + outJar.write(outSrc.toByteArray()) + outJar.closeEntry() } - }.map { future -> - val (path, outSrc) = future.get() - outJar.putNextEntry(ZipEntry(path)) - outJar.write(outSrc.toByteArray()) - outJar.closeEntry() + } finally { + executor.shutdown() } - executor.shutdown() - val cacheSplit = command.protoLogCacheClassNameArg.split(".") val cacheName = cacheSplit.last() val cachePackage = cacheSplit.dropLast(1).joinToString(".") @@ -153,30 +155,32 @@ ${updates.replaceIndent(" ")} val executor = newThreadPool() - command.javaSourceArgs.map { path -> - executor.submitCallable { - val file = File(path) - val text = injector.readText(file) - if (containsProtoLogText(text, command.protoLogClassNameArg)) { - try { - val code = tryParse(text, path) - builder.findLogCalls(code, path, packagePath(file, code)) - } catch (ex: ParsingException) { - // If we cannot parse this file, skip it (and log why). Compilation will fail - // in a subsequent build step. - injector.reportParseError(ex) + try { + command.javaSourceArgs.map { path -> + executor.submitCallable { + val file = File(path) + val text = injector.readText(file) + if (containsProtoLogText(text, command.protoLogClassNameArg)) { + try { + val code = tryParse(text, path) + builder.findLogCalls(code, path, packagePath(file, code)) + } catch (ex: ParsingException) { + // If we cannot parse this file, skip it (and log why). Compilation will + // fail in a subsequent build step. + injector.reportParseError(ex) + null + } + } else { null } - } else { - null } + }.forEach { future -> + builder.addLogCalls(future.get() ?: return@forEach) } - }.forEach { future -> - builder.addLogCalls(future.get() ?: return@forEach) + } finally { + executor.shutdown() } - executor.shutdown() - val out = injector.fileOutputStream(command.viewerConfigJsonArg) out.write(builder.build().toByteArray()) out.close() diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp index 0b82a3dcebc4..7bbac137f998 100644 --- a/tools/stats_log_api_gen/Collation.cpp +++ b/tools/stats_log_api_gen/Collation.cpp @@ -291,6 +291,15 @@ int collate_atom(const Descriptor *atom, AtomDecl *atomDecl, } if (field->options().GetExtension(os::statsd::state_field_option).option() == + os::statsd::StateField::PRIMARY_FIELD_FIRST_UID) { + if (javaType != JAVA_TYPE_ATTRIBUTION_CHAIN) { + errorCount++; + } else { + atomDecl->primaryFields.push_back(FIRST_UID_IN_CHAIN_ID); + } + } + + if (field->options().GetExtension(os::statsd::state_field_option).option() == os::statsd::StateField::EXCLUSIVE) { if (javaType == JAVA_TYPE_UNKNOWN || javaType == JAVA_TYPE_ATTRIBUTION_CHAIN || diff --git a/tools/stats_log_api_gen/Collation.h b/tools/stats_log_api_gen/Collation.h index 3efdd520d7f5..87d4d5db0cee 100644 --- a/tools/stats_log_api_gen/Collation.h +++ b/tools/stats_log_api_gen/Collation.h @@ -36,6 +36,8 @@ using google::protobuf::FieldDescriptor; const int PULL_ATOM_START_ID = 10000; +const int FIRST_UID_IN_CHAIN_ID = 0; + /** * The types for atom parameters. */ diff --git a/tools/stats_log_api_gen/atoms_info_writer.cpp b/tools/stats_log_api_gen/atoms_info_writer.cpp index 54a9982bb5c2..66ae96401974 100644 --- a/tools/stats_log_api_gen/atoms_info_writer.cpp +++ b/tools/stats_log_api_gen/atoms_info_writer.cpp @@ -25,6 +25,8 @@ namespace android { namespace stats_log_api_gen { static void write_atoms_info_header_body(FILE* out, const Atoms& atoms) { + fprintf(out, "static int FIRST_UID_IN_CHAIN = 0;\n"); + fprintf(out, "struct StateAtomFieldOptions {\n"); fprintf(out, " std::vector<int> primaryFields;\n"); fprintf(out, " int exclusiveField;\n"); diff --git a/tools/stats_log_api_gen/java_writer.cpp b/tools/stats_log_api_gen/java_writer.cpp index 7fa47f696b50..b09dcd5efb9c 100644 --- a/tools/stats_log_api_gen/java_writer.cpp +++ b/tools/stats_log_api_gen/java_writer.cpp @@ -142,16 +142,16 @@ static int write_java_methods( fprintf(out, "%s final int count = valueMap.size();\n", indent.c_str()); fprintf(out, - "%s final SparseIntArray intMap = new SparseIntArray();\n", + "%s SparseIntArray intMap = null;\n", indent.c_str()); fprintf(out, - "%s final SparseLongArray longMap = new SparseLongArray();\n", + "%s SparseLongArray longMap = null;\n", indent.c_str()); fprintf(out, - "%s final SparseArray<String> stringMap = new SparseArray<>();\n", + "%s SparseArray<String> stringMap = null;\n", indent.c_str()); fprintf(out, - "%s final SparseArray<Float> floatMap = new SparseArray<>();\n", + "%s SparseArray<Float> floatMap = null;\n", indent.c_str()); fprintf(out, "%s for (int i = 0; i < count; i++) {\n", indent.c_str()); @@ -163,18 +163,42 @@ static int write_java_methods( fprintf(out, "%s if (value instanceof Integer) {\n", indent.c_str()); fprintf(out, + "%s if (null == intMap) {\n", indent.c_str()); + fprintf(out, + "%s intMap = new SparseIntArray();\n", indent.c_str()); + fprintf(out, + "%s }\n", indent.c_str()); + fprintf(out, "%s intMap.put(key, (Integer) value);\n", indent.c_str()); fprintf(out, "%s } else if (value instanceof Long) {\n", indent.c_str()); fprintf(out, + "%s if (null == longMap) {\n", indent.c_str()); + fprintf(out, + "%s longMap = new SparseLongArray();\n", indent.c_str()); + fprintf(out, + "%s }\n", indent.c_str()); + fprintf(out, "%s longMap.put(key, (Long) value);\n", indent.c_str()); fprintf(out, "%s } else if (value instanceof String) {\n", indent.c_str()); fprintf(out, + "%s if (null == stringMap) {\n", indent.c_str()); + fprintf(out, + "%s stringMap = new SparseArray<>();\n", indent.c_str()); + fprintf(out, + "%s }\n", indent.c_str()); + fprintf(out, "%s stringMap.put(key, (String) value);\n", indent.c_str()); fprintf(out, "%s } else if (value instanceof Float) {\n", indent.c_str()); fprintf(out, + "%s if (null == floatMap) {\n", indent.c_str()); + fprintf(out, + "%s floatMap = new SparseArray<>();\n", indent.c_str()); + fprintf(out, + "%s }\n", indent.c_str()); + fprintf(out, "%s floatMap.put(key, (Float) value);\n", indent.c_str()); fprintf(out, "%s }\n", indent.c_str()); diff --git a/wifi/Android.bp b/wifi/Android.bp index 6326f14bc6fd..4c9ee854536e 100644 --- a/wifi/Android.bp +++ b/wifi/Android.bp @@ -50,6 +50,8 @@ test_access_hidden_api_whitelist = [ "//frameworks/opt/net/wifi/libs/WifiTrackerLib/tests", "//external/robolectric-shadows:__subpackages__", + "//frameworks/base/packages/SettingsLib/tests/integ", + "//external/sl4a:__subpackages__", ] // wifi-service needs pre-jarjared version of framework-wifi so it can reference copied utility @@ -68,6 +70,7 @@ java_library { "framework-annotations-lib", "unsupportedappusage", // for android.compat.annotation.UnsupportedAppUsage "unsupportedappusage-annotation", // for dalvik.annotation.compat.UnsupportedAppUsage + "framework-telephony-stubs", ], srcs: [ ":framework-wifi-updatable-sources", diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index f490766559de..67f166327b56 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -92,6 +92,8 @@ interface IWifiManager void allowAutojoinPasspoint(String fqdn, boolean enableAutoJoin); + void setMacRandomizationSettingPasspointEnabled(String fqdn, boolean enable); + boolean startScan(String packageName, String featureId); List<ScanResult> getScanResults(String callingPackage, String callingFeatureId); diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java index c6aca07ebe94..341330587614 100644 --- a/wifi/java/android/net/wifi/ScanResult.java +++ b/wifi/java/android/net/wifi/ScanResult.java @@ -23,6 +23,8 @@ import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.annotations.VisibleForTesting; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -793,10 +795,19 @@ public class ScanResult implements Parcelable { } } - /** empty scan result + /** + * Construct an empty scan result. * - * {@hide} - * */ + * Test code has a need to construct a ScanResult in a specific state. + * (Note that mocking using Mockito does not work if the object needs to be parceled and + * unparceled.) + * Export a @SystemApi default constructor to allow tests to construct an empty ScanResult + * object. The test can then directly set the fields it cares about. + * + * @hide + */ + @SystemApi + @VisibleForTesting public ScanResult() { } diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java index f4c5b9168cd0..b2fbb401dde5 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -29,6 +29,7 @@ import android.net.NetworkSpecifier; import android.net.ProxyInfo; import android.net.StaticIpConfiguration; import android.net.Uri; +import android.net.util.MacAddressUtils; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; @@ -39,6 +40,8 @@ import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; +import com.android.internal.annotations.VisibleForTesting; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; @@ -370,7 +373,6 @@ public class WifiConfiguration implements Parcelable { * ECDHE_ECDSA * ECDHE_RSA * </pre> - * @hide */ public static class SuiteBCipher { private SuiteBCipher() { } @@ -854,18 +856,6 @@ public class WifiConfiguration implements Parcelable { /** * @hide - * For debug: date at which the config was last updated - */ - public String updateTime; - - /** - * @hide - * For debug: date at which the config was last updated - */ - public String creationTime; - - /** - * @hide * The WiFi configuration is considered to have no internet access for purpose of autojoining * if there has been a report of it having no internet access, and, it never have had * internet access in the past. @@ -1164,7 +1154,7 @@ public class WifiConfiguration implements Parcelable { * @return true if mac is good to use */ public static boolean isValidMacAddressForRandomization(MacAddress mac) { - return mac != null && !mac.isMulticastAddress() && mac.isLocallyAssigned() + return mac != null && !MacAddressUtils.isMulticastAddress(mac) && mac.isLocallyAssigned() && !MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS).equals(mac); } @@ -1208,22 +1198,27 @@ public class WifiConfiguration implements Parcelable { */ @SystemApi public static class NetworkSelectionStatus { - // Quality Network Selection Status enable, temporary disabled, permanently disabled + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "NETWORK_SELECTION_", + value = { + NETWORK_SELECTION_ENABLED, + NETWORK_SELECTION_TEMPORARY_DISABLED, + NETWORK_SELECTION_PERMANENTLY_DISABLED}) + public @interface NetworkEnabledStatus {} /** - * This network is allowed to join Quality Network Selection - * @hide + * This network will be considered as a potential candidate to connect to during network + * selection. */ public static final int NETWORK_SELECTION_ENABLED = 0; /** - * network was temporary disabled. Can be re-enabled after a time period expire - * @hide + * This network was temporary disabled. May be re-enabled after a time out. */ - public static final int NETWORK_SELECTION_TEMPORARY_DISABLED = 1; + public static final int NETWORK_SELECTION_TEMPORARY_DISABLED = 1; /** - * network was permanently disabled. - * @hide + * This network was permanently disabled. */ - public static final int NETWORK_SELECTION_PERMANENTLY_DISABLED = 2; + public static final int NETWORK_SELECTION_PERMANENTLY_DISABLED = 2; /** * Maximum Network selection status * @hide @@ -1455,6 +1450,7 @@ public class WifiConfiguration implements Parcelable { * Network selection status, should be in one of three status: enable, temporaily disabled * or permanently disabled */ + @NetworkEnabledStatus private int mStatus; /** @@ -1485,12 +1481,6 @@ public class WifiConfiguration implements Parcelable { private String mConnectChoice; /** - * The system timestamp when we records the connectChoice. This value is obtained from - * System.currentTimeMillis - */ - private long mConnectChoiceTimestamp = INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP; - - /** * Used to cache the temporary candidate during the network selection procedure. It will be * kept updating once a new scan result has a higher score than current one */ @@ -1593,25 +1583,6 @@ public class WifiConfiguration implements Parcelable { mConnectChoice = newConnectChoice; } - /** - * get the timeStamp when user select a choice over this configuration - * @return returns when current connectChoice is set (time from System.currentTimeMillis) - * @hide - */ - public long getConnectChoiceTimestamp() { - return mConnectChoiceTimestamp; - } - - /** - * set the timeStamp when user select a choice over this configuration - * @param timeStamp, the timestamp set to connectChoiceTimestamp, expected timestamp should - * be obtained from System.currentTimeMillis - * @hide - */ - public void setConnectChoiceTimestamp(long timeStamp) { - mConnectChoiceTimestamp = timeStamp; - } - /** Get the current Quality network selection status as a String (for debugging). */ @NonNull public String getNetworkStatusString() { @@ -1635,6 +1606,56 @@ public class WifiConfiguration implements Parcelable { } /** + * NetworkSelectionStatus exports an immutable public API. + * However, test code has a need to construct a NetworkSelectionStatus in a specific state. + * (Note that mocking using Mockito does not work if the object needs to be parceled and + * unparceled.) + * Export a @SystemApi Builder to allow tests to construct a NetworkSelectionStatus object + * in the desired state, without sacrificing NetworkSelectionStatus's immutability. + */ + @VisibleForTesting + public static final class Builder { + private final NetworkSelectionStatus mNetworkSelectionStatus = + new NetworkSelectionStatus(); + + /** + * Set the current network selection status. + * One of: + * {@link #NETWORK_SELECTION_ENABLED}, + * {@link #NETWORK_SELECTION_TEMPORARY_DISABLED}, + * {@link #NETWORK_SELECTION_PERMANENTLY_DISABLED} + * @see NetworkSelectionStatus#getNetworkSelectionStatus() + */ + @NonNull + public Builder setNetworkSelectionStatus(@NetworkEnabledStatus int status) { + mNetworkSelectionStatus.setNetworkSelectionStatus(status); + return this; + } + + /** + * + * Set the current network's disable reason. + * One of the {@link #NETWORK_SELECTION_ENABLE} or DISABLED_* constants. + * e.g. {@link #DISABLED_ASSOCIATION_REJECTION}. + * @see NetworkSelectionStatus#getNetworkSelectionDisableReason() + */ + @NonNull + public Builder setNetworkSelectionDisableReason( + @NetworkSelectionDisableReason int reason) { + mNetworkSelectionStatus.setNetworkSelectionDisableReason(reason); + return this; + } + + /** + * Build a NetworkSelectionStatus object. + */ + @NonNull + public NetworkSelectionStatus build() { + return mNetworkSelectionStatus; + } + } + + /** * Get the network disable reason string for a reason code (for debugging). * @param reason specific error reason. One of the {@link #NETWORK_SELECTION_ENABLE} or * DISABLED_* constants e.g. {@link #DISABLED_ASSOCIATION_REJECTION}. @@ -1660,10 +1681,13 @@ public class WifiConfiguration implements Parcelable { } /** - * get current network network selection status - * @return return current network network selection status - * @hide + * Get the current network network selection status. + * One of: + * {@link #NETWORK_SELECTION_ENABLED}, + * {@link #NETWORK_SELECTION_TEMPORARY_DISABLED}, + * {@link #NETWORK_SELECTION_PERMANENTLY_DISABLED} */ + @NetworkEnabledStatus public int getNetworkSelectionStatus() { return mStatus; } @@ -1841,7 +1865,6 @@ public class WifiConfiguration implements Parcelable { setCandidate(source.getCandidate()); setCandidateScore(source.getCandidateScore()); setConnectChoice(source.getConnectChoice()); - setConnectChoiceTimestamp(source.getConnectChoiceTimestamp()); setHasEverConnected(source.getHasEverConnected()); } @@ -1858,7 +1881,6 @@ public class WifiConfiguration implements Parcelable { if (getConnectChoice() != null) { dest.writeInt(CONNECT_CHOICE_EXISTS); dest.writeString(getConnectChoice()); - dest.writeLong(getConnectChoiceTimestamp()); } else { dest.writeInt(CONNECT_CHOICE_NOT_EXISTS); } @@ -1877,10 +1899,8 @@ public class WifiConfiguration implements Parcelable { setNetworkSelectionBSSID(in.readString()); if (in.readInt() == CONNECT_CHOICE_EXISTS) { setConnectChoice(in.readString()); - setConnectChoiceTimestamp(in.readLong()); } else { setConnectChoice(null); - setConnectChoiceTimestamp(INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP); } setHasEverConnected(in.readInt() != 0); } @@ -1965,10 +1985,11 @@ public class WifiConfiguration implements Parcelable { } /** - * Set the network selection status + * Set the network selection status. * @hide */ - public void setNetworkSelectionStatus(NetworkSelectionStatus status) { + @SystemApi + public void setNetworkSelectionStatus(@NonNull NetworkSelectionStatus status) { mNetworkSelectionStatus = status; } @@ -2103,9 +2124,6 @@ public class WifiConfiguration implements Parcelable { } if (mNetworkSelectionStatus.getConnectChoice() != null) { sbuf.append(" connect choice: ").append(mNetworkSelectionStatus.getConnectChoice()); - sbuf.append(" connect choice set time: ") - .append(logTimeOfDay( - mNetworkSelectionStatus.getConnectChoiceTimestamp())); } sbuf.append(" hasEverConnected: ") .append(mNetworkSelectionStatus.getHasEverConnected()).append("\n"); @@ -2117,12 +2135,6 @@ public class WifiConfiguration implements Parcelable { sbuf.append(" numNoInternetAccessReports "); sbuf.append(this.numNoInternetAccessReports).append("\n"); } - if (this.updateTime != null) { - sbuf.append(" update ").append(this.updateTime).append("\n"); - } - if (this.creationTime != null) { - sbuf.append(" creation ").append(this.creationTime).append("\n"); - } if (this.validatedInternetAccess) sbuf.append(" validatedInternetAccess"); if (this.ephemeral) sbuf.append(" ephemeral"); if (this.osu) sbuf.append(" osu"); @@ -2669,8 +2681,6 @@ public class WifiConfiguration implements Parcelable { allowAutojoin = source.allowAutojoin; numNoInternetAccessReports = source.numNoInternetAccessReports; noInternetAccessExpected = source.noInternetAccessExpected; - creationTime = source.creationTime; - updateTime = source.updateTime; shared = source.shared; recentFailure.setAssociationStatus(source.recentFailure.getAssociationStatus()); mRandomizedMacAddress = source.mRandomizedMacAddress; diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java index 41f7c6e2bb0d..5edcc2df3804 100644 --- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java +++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java @@ -1028,8 +1028,10 @@ public class WifiEnterpriseConfig implements Parcelable { } /** - * @hide + * Get the client private key as supplied in {@link #setClientKeyEntryWithCertificateChain}, or + * null if unset. */ + @Nullable public PrivateKey getClientPrivateKey() { return mClientPrivateKey; } diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java index 62337cbf7e22..7cd00b9dbb56 100644 --- a/wifi/java/android/net/wifi/WifiInfo.java +++ b/wifi/java/android/net/wifi/WifiInfo.java @@ -17,6 +17,7 @@ package android.net.wifi; import android.annotation.IntRange; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; @@ -27,6 +28,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import com.android.internal.annotations.VisibleForTesting; + import java.net.Inet4Address; import java.net.InetAddress; import java.net.UnknownHostException; @@ -356,6 +359,72 @@ public class WifiInfo implements Parcelable { } } + /** + * WifiInfo exports an immutable public API. + * However, test code has a need to construct a WifiInfo in a specific state. + * (Note that mocking using Mockito does not work if the object needs to be parceled and + * unparceled.) + * Export a @SystemApi Builder to allow tests to construct a WifiInfo object + * in the desired state, without sacrificing WifiInfo's immutability. + * + * @hide + */ + // This builder was not made public to reduce confusion for external developers as there are + // no legitimate uses for this builder except for testing. + @SystemApi + @VisibleForTesting + public static final class Builder { + private final WifiInfo mWifiInfo = new WifiInfo(); + + /** + * Set the SSID, in the form of a raw byte array. + * @see WifiInfo#getSSID() + */ + @NonNull + public Builder setSsid(@NonNull byte[] ssid) { + mWifiInfo.setSSID(WifiSsid.createFromByteArray(ssid)); + return this; + } + + /** + * Set the BSSID. + * @see WifiInfo#getBSSID() + */ + @NonNull + public Builder setBssid(@NonNull String bssid) { + mWifiInfo.setBSSID(bssid); + return this; + } + + /** + * Set the RSSI, in dBm. + * @see WifiInfo#getRssi() + */ + @NonNull + public Builder setRssi(int rssi) { + mWifiInfo.setRssi(rssi); + return this; + } + + /** + * Set the network ID. + * @see WifiInfo#getNetworkId() + */ + @NonNull + public Builder setNetworkId(int networkId) { + mWifiInfo.setNetworkId(networkId); + return this; + } + + /** + * Build a WifiInfo object. + */ + @NonNull + public WifiInfo build() { + return mWifiInfo; + } + } + /** @hide */ public void setSSID(WifiSsid wifiSsid) { mWifiSsid = wifiSsid; @@ -631,6 +700,11 @@ public class WifiInfo implements Parcelable { /** * Returns the Fully Qualified Domain Name of the network if it is a Passpoint network. + * <p> + * The FQDN may be + * <lt>{@code null} if no network currently connected, currently connected network is not + * passpoint network or the caller has insufficient permissions to access the FQDN.</lt> + * </p> */ public @Nullable String getPasspointFqdn() { return mFqdn; @@ -643,6 +717,12 @@ public class WifiInfo implements Parcelable { /** * Returns the Provider Friendly Name of the network if it is a Passpoint network. + * <p> + * The Provider Friendly Name may be + * <lt>{@code null} if no network currently connected, currently connected network is not + * passpoint network or the caller has insufficient permissions to access the Provider Friendly + * Name. </lt> + * </p> */ public @Nullable String getPasspointProviderFriendlyName() { return mProviderFriendlyName; diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index a8a31eba303c..ec3de43ee85a 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -2369,6 +2369,15 @@ public class WifiManager { } /** + * Query whether the device supports Station (STA) + Access point (AP) concurrency or not. + * + * @return true if this device supports STA + AP concurrency, false otherwise. + */ + public boolean isStaApConcurrencySupported() { + return isFeatureSupported(WIFI_FEATURE_AP_STA); + } + + /** * @deprecated Please use {@link android.content.pm.PackageManager#hasSystemFeature(String)} * with {@link android.content.pm.PackageManager#FEATURE_WIFI_RTT} and * {@link android.content.pm.PackageManager#FEATURE_WIFI_AWARE}. @@ -2606,6 +2615,8 @@ public class WifiManager { * the same permissions as {@link #getScanResults}. If such access is not allowed, * {@link WifiInfo#getSSID} will return {@link #UNKNOWN_SSID} and * {@link WifiInfo#getBSSID} will return {@code "02:00:00:00:00:00"}. + * {@link WifiInfo#getPasspointFqdn()} will return null. + * {@link WifiInfo#getPasspointProviderFriendlyName()} will return null. * * @return the Wi-Fi information, contained in {@link WifiInfo}. */ @@ -4232,6 +4243,24 @@ public class WifiManager { } /** + * Configure MAC randomization setting for a Passpoint profile. + * MAC randomization is enabled by default. + * + * @param fqdn the FQDN (fully qualified domain name) of the passpoint profile. + * @param enable true to enable MAC randomization, false to disable MAC randomization. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) + public void setMacRandomizationSettingPasspointEnabled(@NonNull String fqdn, boolean enable) { + try { + mService.setMacRandomizationSettingPasspointEnabled(fqdn, enable); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Disable an ephemeral network. * * @param ssid in the format of WifiConfiguration's SSID. diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java index c0e089090dc9..2fba5a3f4624 100644 --- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java +++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java @@ -122,6 +122,12 @@ public final class WifiNetworkSuggestion implements Parcelable { * Whether the setCredentialSharedWithUser have been called. */ private boolean mIsSharedWithUserSet; + + /** + * Whether this network is initialized with auto-join enabled (the default) or not. + */ + private boolean mIsInitialAutoJoinEnabled; + /** * Pre-shared key for use with WAPI-PSK networks. */ @@ -148,6 +154,7 @@ public final class WifiNetworkSuggestion implements Parcelable { mIsMetered = false; mIsSharedWithUser = true; mIsSharedWithUserSet = false; + mIsInitialAutoJoinEnabled = true; mPriority = UNASSIGNED_PRIORITY; mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID; mWapiPskPassphrase = null; @@ -440,6 +447,27 @@ public final class WifiNetworkSuggestion implements Parcelable { return this; } + /** + * Specifies whether the suggestion is created with auto-join enabled or disabled. The + * user may modify the auto-join configuration of a suggestion directly once the device + * associates to the network. + * <p> + * If auto-join is initialized as disabled the user may still be able to manually connect + * to the network. Therefore, disabling auto-join only makes sense if + * {@link #setCredentialSharedWithUser(boolean)} is set to true (the default) which + * itself implies a secure (non-open) network. + * <p> + * If not set, defaults to true (i.e. auto-join is initialized as enabled). + * + * @param enabled true for initializing with auto-join enabled (the default), false to + * initializing with auto-join disabled. + * @return Instance of (@link {@link Builder} to enable chaining of the builder method. + */ + public @NonNull Builder setIsInitialAutoJoinEnabled(boolean enabled) { + mIsInitialAutoJoinEnabled = enabled; + return this; + } + private void setSecurityParamsInWifiConfiguration( @NonNull WifiConfiguration configuration) { if (!TextUtils.isEmpty(mWpa2PskPassphrase)) { // WPA-PSK network. @@ -587,7 +615,6 @@ public final class WifiNetworkSuggestion implements Parcelable { + "suggestion with Passpoint configuration"); } wifiConfiguration = buildWifiConfigurationForPasspoint(); - } else { if (mSsid == null) { throw new IllegalStateException("setSsid should be invoked for suggestion"); @@ -608,6 +635,12 @@ public final class WifiNetworkSuggestion implements Parcelable { } mIsSharedWithUser = false; } + + if (!mIsSharedWithUser && !mIsInitialAutoJoinEnabled) { + throw new IllegalStateException("Should have not a network with both " + + "setIsUserAllowedToManuallyConnect and " + + "setIsAutoJoinEnabled set to false"); + } } return new WifiNetworkSuggestion( @@ -615,7 +648,8 @@ public final class WifiNetworkSuggestion implements Parcelable { mPasspointConfiguration, mIsAppInteractionRequired, mIsUserInteractionRequired, - mIsSharedWithUser); + mIsSharedWithUser, + mIsInitialAutoJoinEnabled); } } @@ -642,6 +676,7 @@ public final class WifiNetworkSuggestion implements Parcelable { * @hide */ public final boolean isUserInteractionRequired; + /** * Whether app share credential with the user, allow user use provided credential to * connect network manually. @@ -649,6 +684,12 @@ public final class WifiNetworkSuggestion implements Parcelable { */ public final boolean isUserAllowedToManuallyConnect; + /** + * Whether the suggestion will be initialized as auto-joined or not. + * @hide + */ + public final boolean isInitialAutoJoinEnabled; + /** @hide */ public WifiNetworkSuggestion() { this.wifiConfiguration = null; @@ -656,6 +697,7 @@ public final class WifiNetworkSuggestion implements Parcelable { this.isAppInteractionRequired = false; this.isUserInteractionRequired = false; this.isUserAllowedToManuallyConnect = true; + this.isInitialAutoJoinEnabled = true; } /** @hide */ @@ -663,7 +705,8 @@ public final class WifiNetworkSuggestion implements Parcelable { @Nullable PasspointConfiguration passpointConfiguration, boolean isAppInteractionRequired, boolean isUserInteractionRequired, - boolean isUserAllowedToManuallyConnect) { + boolean isUserAllowedToManuallyConnect, + boolean isInitialAutoJoinEnabled) { checkNotNull(networkConfiguration); this.wifiConfiguration = networkConfiguration; this.passpointConfiguration = passpointConfiguration; @@ -671,6 +714,7 @@ public final class WifiNetworkSuggestion implements Parcelable { this.isAppInteractionRequired = isAppInteractionRequired; this.isUserInteractionRequired = isUserInteractionRequired; this.isUserAllowedToManuallyConnect = isUserAllowedToManuallyConnect; + this.isInitialAutoJoinEnabled = isInitialAutoJoinEnabled; } public static final @NonNull Creator<WifiNetworkSuggestion> CREATOR = @@ -682,7 +726,8 @@ public final class WifiNetworkSuggestion implements Parcelable { in.readParcelable(null), // PasspointConfiguration in.readBoolean(), // isAppInteractionRequired in.readBoolean(), // isUserInteractionRequired - in.readBoolean() // isSharedCredentialWithUser + in.readBoolean(), // isSharedCredentialWithUser + in.readBoolean() // isAutoJoinEnabled ); } @@ -704,6 +749,7 @@ public final class WifiNetworkSuggestion implements Parcelable { dest.writeBoolean(isAppInteractionRequired); dest.writeBoolean(isUserInteractionRequired); dest.writeBoolean(isUserAllowedToManuallyConnect); + dest.writeBoolean(isInitialAutoJoinEnabled); } @Override @@ -744,6 +790,7 @@ public final class WifiNetworkSuggestion implements Parcelable { .append(", isAppInteractionRequired=").append(isAppInteractionRequired) .append(", isUserInteractionRequired=").append(isUserInteractionRequired) .append(", isUserAllowedToManuallyConnect=").append(isUserAllowedToManuallyConnect) + .append(", isInitialAutoJoinEnabled=").append(isInitialAutoJoinEnabled) .append(" ]"); return sb.toString(); } diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java index 1822e84fdd57..7c335fc323f5 100644 --- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java +++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java @@ -431,6 +431,13 @@ public final class PasspointConfiguration implements Parcelable { private boolean mIsAutoJoinEnabled = true; /** + * The mac randomization setting specifies whether a randomized or device MAC address will + * be used to connect to the passpoint network. If true, a randomized MAC will be used. + * Otherwise, the device MAC address will be used. + */ + private boolean mIsMacRandomizationEnabled = true; + + /** * Configures the auto-association status of this Passpoint configuration. A value of true * indicates that the configuration will be considered for auto-connection, a value of false * indicates that only manual connection will work - the framework will not auto-associate to @@ -444,6 +451,18 @@ public final class PasspointConfiguration implements Parcelable { } /** + * Configures the MAC randomization setting for this Passpoint configuration. + * If set to true, the framework will use a randomized MAC address to connect to this Passpoint + * network. Otherwise, the framework will use the device MAC address. + * + * @param enabled true to use randomized MAC address, false to use device MAC address. + * @hide + */ + public void setMacRandomizationEnabled(boolean enabled) { + mIsMacRandomizationEnabled = enabled; + } + + /** * Indicates whether the Passpoint configuration may be auto-connected to by the framework. A * value of true indicates that auto-connection can happen, a value of false indicates that it * cannot. However, even when auto-connection is not possible manual connection by the user is @@ -459,6 +478,19 @@ public final class PasspointConfiguration implements Parcelable { } /** + * Indicates whether a randomized MAC address or device MAC address will be used for + * connections to this Passpoint network. If true, a randomized MAC address will be used. + * Otherwise, the device MAC address will be used. + * + * @return true for MAC randomization enabled. False for disabled. + * @hide + */ + @SystemApi + public boolean isMacRandomizationEnabled() { + return mIsMacRandomizationEnabled; + } + + /** * Constructor for creating PasspointConfiguration with default values. */ public PasspointConfiguration() {} @@ -501,6 +533,7 @@ public final class PasspointConfiguration implements Parcelable { mAaaServerTrustedNames = source.mAaaServerTrustedNames; mCarrierId = source.mCarrierId; mIsAutoJoinEnabled = source.mIsAutoJoinEnabled; + mIsMacRandomizationEnabled = source.mIsMacRandomizationEnabled; } @Override @@ -531,6 +564,7 @@ public final class PasspointConfiguration implements Parcelable { dest.writeBundle(bundle); dest.writeInt(mCarrierId); dest.writeBoolean(mIsAutoJoinEnabled); + dest.writeBoolean(mIsMacRandomizationEnabled); } @Override @@ -562,6 +596,7 @@ public final class PasspointConfiguration implements Parcelable { && mUsageLimitTimeLimitInMinutes == that.mUsageLimitTimeLimitInMinutes && mCarrierId == that.mCarrierId && mIsAutoJoinEnabled == that.mIsAutoJoinEnabled + && mIsMacRandomizationEnabled == that.mIsMacRandomizationEnabled && (mServiceFriendlyNames == null ? that.mServiceFriendlyNames == null : mServiceFriendlyNames.equals(that.mServiceFriendlyNames)); } @@ -572,7 +607,7 @@ public final class PasspointConfiguration implements Parcelable { mUpdateIdentifier, mCredentialPriority, mSubscriptionCreationTimeInMillis, mSubscriptionExpirationTimeInMillis, mUsageLimitUsageTimePeriodInMinutes, mUsageLimitStartTimeInMillis, mUsageLimitDataLimit, mUsageLimitTimeLimitInMinutes, - mServiceFriendlyNames, mCarrierId, mIsAutoJoinEnabled); + mServiceFriendlyNames, mCarrierId, mIsAutoJoinEnabled, mIsMacRandomizationEnabled); } @Override @@ -627,6 +662,7 @@ public final class PasspointConfiguration implements Parcelable { } builder.append("CarrierId:" + mCarrierId); builder.append("IsAutoJoinEnabled:" + mIsAutoJoinEnabled); + builder.append("mIsMacRandomizationEnabled:" + mIsMacRandomizationEnabled); return builder.toString(); } @@ -733,6 +769,7 @@ public final class PasspointConfiguration implements Parcelable { config.setServiceFriendlyNames(friendlyNamesMap); config.mCarrierId = in.readInt(); config.mIsAutoJoinEnabled = in.readBoolean(); + config.mIsMacRandomizationEnabled = in.readBoolean(); return config; } diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceInfo.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceInfo.java index 0de7ba6439eb..dad431c1ca2c 100644 --- a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceInfo.java +++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceInfo.java @@ -17,7 +17,7 @@ package android.net.wifi.p2p.nsd; import android.compat.annotation.UnsupportedAppUsage; -import android.net.nsd.DnsSdTxtRecord; +import android.net.util.nsd.DnsSdTxtRecord; import android.os.Build; import android.text.TextUtils; diff --git a/wifi/java/com/android/server/wifi/BaseWifiService.java b/wifi/java/com/android/server/wifi/BaseWifiService.java index 3c13562d6952..1cf3825d2397 100644 --- a/wifi/java/com/android/server/wifi/BaseWifiService.java +++ b/wifi/java/com/android/server/wifi/BaseWifiService.java @@ -187,6 +187,11 @@ public class BaseWifiService extends IWifiManager.Stub { } @Override + public void setMacRandomizationSettingPasspointEnabled(String fqdn, boolean enable) { + throw new UnsupportedOperationException(); + } + + @Override public boolean startScan(String packageName, String featureId) { throw new UnsupportedOperationException(); } diff --git a/wifi/tests/Android.bp b/wifi/tests/Android.bp new file mode 100644 index 000000000000..6a39959e8cfd --- /dev/null +++ b/wifi/tests/Android.bp @@ -0,0 +1,50 @@ +// 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. + +// Make test APK +// ============================================================ + +android_test { + name: "FrameworksWifiApiTests", + + defaults: ["framework-wifi-test-defaults"], + + srcs: ["**/*.java"], + + jacoco: { + include_filter: ["android.net.wifi.*"], + // TODO(b/147521214) need to exclude test classes + exclude_filter: [], + }, + + static_libs: [ + "androidx.test.rules", + "core-test-rules", + "guava", + "mockito-target-minus-junit4", + "net-tests-utils", + "frameworks-base-testutils", + "truth-prebuilt", + ], + + libs: [ + "android.test.runner", + "android.test.base", + ], + + test_suites: [ + "device-tests", + "mts", + ], +} diff --git a/wifi/tests/Android.mk b/wifi/tests/Android.mk deleted file mode 100644 index d2c385b46eb1..000000000000 --- a/wifi/tests/Android.mk +++ /dev/null @@ -1,66 +0,0 @@ -# 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. -# 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. - -LOCAL_PATH:= $(call my-dir) - -# Make test APK -# ============================================================ -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := tests - -LOCAL_SRC_FILES := $(call all-subdir-java-files) - -# This list is generated from the java source files in this module -# The list is a comma separated list of class names with * matching zero or more characters. -# Example: -# Input files: src/com/android/server/wifi/Test.java src/com/android/server/wifi/AnotherTest.java -# Generated exclude list: com.android.server.wifi.Test*,com.android.server.wifi.AnotherTest* - -# Filter all src files to just java files -local_java_files := $(filter %.java,$(LOCAL_SRC_FILES)) -# Transform java file names into full class names. -# This only works if the class name matches the file name and the directory structure -# matches the package. -local_classes := $(subst /,.,$(patsubst src/%.java,%,$(local_java_files))) -# Convert class name list to jacoco exclude list -# This appends a * to all classes and replace the space separators with commas. -# These patterns will match all classes in this module and their inner classes. -jacoco_exclude := $(subst $(space),$(comma),$(patsubst %,%*,$(local_classes))) - -jacoco_include := android.net.wifi.* - -LOCAL_JACK_COVERAGE_INCLUDE_FILTER := $(jacoco_include) -LOCAL_JACK_COVERAGE_EXCLUDE_FILTER := $(jacoco_exclude) - -LOCAL_STATIC_JAVA_LIBRARIES := \ - androidx.test.rules \ - core-test-rules \ - guava \ - mockito-target-minus-junit4 \ - net-tests-utils \ - frameworks-base-testutils \ - truth-prebuilt \ - -LOCAL_JAVA_LIBRARIES := \ - android.test.runner \ - android.test.base \ - -LOCAL_PACKAGE_NAME := FrameworksWifiApiTests -LOCAL_PRIVATE_PLATFORM_APIS := true -LOCAL_COMPATIBILITY_SUITE := \ - device-tests \ - mts \ - -include $(BUILD_PACKAGE) diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java index 8689a38c6b17..0ef75aa3eb5a 100644 --- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java +++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java @@ -27,6 +27,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import android.net.MacAddress; +import android.net.util.MacAddressUtils; import android.net.wifi.WifiConfiguration.KeyMgmt; import android.net.wifi.WifiConfiguration.NetworkSelectionStatus; import android.os.Parcel; @@ -63,7 +64,7 @@ public class WifiConfigurationTest { config.updateIdentifier = "1234"; config.fromWifiNetworkSpecifier = true; config.fromWifiNetworkSuggestion = true; - config.setRandomizedMacAddress(MacAddress.createRandomUnicastAddress()); + config.setRandomizedMacAddress(MacAddressUtils.createRandomUnicastAddress()); MacAddress macBeforeParcel = config.getRandomizedMacAddress(); Parcel parcelW = Parcel.obtain(); config.writeToParcel(parcelW, 0); @@ -169,7 +170,7 @@ public class WifiConfigurationTest { MacAddress defaultMac = MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS); assertEquals(defaultMac, config.getRandomizedMacAddress()); - MacAddress macToChangeInto = MacAddress.createRandomUnicastAddress(); + MacAddress macToChangeInto = MacAddressUtils.createRandomUnicastAddress(); config.setRandomizedMacAddress(macToChangeInto); MacAddress macAfterChange = config.getRandomizedMacAddress(); diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java index 5bdc34402cc4..983ac8216124 100644 --- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java +++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java @@ -1706,6 +1706,17 @@ public class WifiManagerTest { verify(mWifiService).allowAutojoinPasspoint(fqdn, true); } + /** + * Test behavior of + * {@link WifiManager#setMacRandomizationSettingPasspointEnabled(String, boolean)} + */ + @Test + public void testSetMacRandomizationSettingPasspointEnabled() throws Exception { + final String fqdn = "FullyQualifiedDomainName"; + mWifiManager.setMacRandomizationSettingPasspointEnabled(fqdn, true); + verify(mWifiService).setMacRandomizationSettingPasspointEnabled(fqdn, true); + } + /** * Test behavior of {@link WifiManager#disconnect()} diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java index ac915447f3c4..cb1b7747798d 100644 --- a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java +++ b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java @@ -32,8 +32,6 @@ import org.junit.Test; */ @SmallTest public class WifiNetworkSuggestionTest { - private static final int TEST_UID = 45677; - private static final int TEST_UID_OTHER = 45673; private static final String TEST_SSID = "\"Test123\""; private static final String TEST_BSSID = "12:12:12:12:12:12"; private static final String TEST_SSID_1 = "\"Test1234\""; @@ -61,7 +59,8 @@ public class WifiNetworkSuggestionTest { assertEquals(WifiConfiguration.METERED_OVERRIDE_NONE, suggestion.wifiConfiguration.meteredOverride); assertEquals(-1, suggestion.wifiConfiguration.priority); - assertEquals(false, suggestion.isUserAllowedToManuallyConnect); + assertFalse(suggestion.isUserAllowedToManuallyConnect); + assertTrue(suggestion.isInitialAutoJoinEnabled); } /** @@ -90,7 +89,8 @@ public class WifiNetworkSuggestionTest { assertEquals(WifiConfiguration.METERED_OVERRIDE_NONE, suggestion.wifiConfiguration.meteredOverride); assertEquals(0, suggestion.wifiConfiguration.priority); - assertEquals(false, suggestion.isUserAllowedToManuallyConnect); + assertFalse(suggestion.isUserAllowedToManuallyConnect); + assertTrue(suggestion.isInitialAutoJoinEnabled); } /** @@ -105,6 +105,7 @@ public class WifiNetworkSuggestionTest { .setSsid(TEST_SSID) .setWpa2Passphrase(TEST_PRESHARED_KEY) .setIsUserInteractionRequired(true) + .setIsInitialAutoJoinEnabled(false) .setIsMetered(true) .build(); @@ -119,6 +120,7 @@ public class WifiNetworkSuggestionTest { suggestion.wifiConfiguration.meteredOverride); assertEquals(-1, suggestion.wifiConfiguration.priority); assertTrue(suggestion.isUserAllowedToManuallyConnect); + assertFalse(suggestion.isInitialAutoJoinEnabled); } /** @@ -140,6 +142,7 @@ public class WifiNetworkSuggestionTest { assertNull(suggestion.wifiConfiguration.preSharedKey); assertTrue(suggestion.wifiConfiguration.requirePMF); assertFalse(suggestion.isUserAllowedToManuallyConnect); + assertTrue(suggestion.isInitialAutoJoinEnabled); } /** @@ -152,6 +155,7 @@ public class WifiNetworkSuggestionTest { .setSsid(TEST_SSID) .setWpa3Passphrase(TEST_PRESHARED_KEY) .setCredentialSharedWithUser(true) + .setIsInitialAutoJoinEnabled(false) .build(); assertEquals("\"" + TEST_SSID + "\"", suggestion.wifiConfiguration.SSID); @@ -161,6 +165,7 @@ public class WifiNetworkSuggestionTest { suggestion.wifiConfiguration.preSharedKey); assertTrue(suggestion.wifiConfiguration.requirePMF); assertTrue(suggestion.isUserAllowedToManuallyConnect); + assertFalse(suggestion.isInitialAutoJoinEnabled); } @@ -191,6 +196,7 @@ public class WifiNetworkSuggestionTest { // allowedSuiteBCiphers are set according to the loaded certificate and cannot be tested // here. assertTrue(suggestion.isUserAllowedToManuallyConnect); + assertTrue(suggestion.isInitialAutoJoinEnabled); } /** @@ -526,7 +532,7 @@ public class WifiNetworkSuggestionTest { configuration.BSSID = TEST_BSSID; configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion( - configuration, null, false, true, true); + configuration, null, false, true, true, false); Parcel parcelW = Parcel.obtain(); suggestion.writeToParcel(parcelW, 0); @@ -548,6 +554,8 @@ public class WifiNetworkSuggestionTest { parcelSuggestion.isAppInteractionRequired); assertEquals(suggestion.isUserInteractionRequired, parcelSuggestion.isUserInteractionRequired); + assertEquals(suggestion.isInitialAutoJoinEnabled, + parcelSuggestion.isInitialAutoJoinEnabled); } /** @@ -580,6 +588,8 @@ public class WifiNetworkSuggestionTest { parcelSuggestion.isAppInteractionRequired); assertEquals(suggestion.isUserInteractionRequired, parcelSuggestion.isUserInteractionRequired); + assertEquals(suggestion.isInitialAutoJoinEnabled, + parcelSuggestion.isInitialAutoJoinEnabled); } /** @@ -593,14 +603,14 @@ public class WifiNetworkSuggestionTest { configuration.BSSID = TEST_BSSID; configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); WifiNetworkSuggestion suggestion = - new WifiNetworkSuggestion(configuration, null, true, false, true); + new WifiNetworkSuggestion(configuration, null, true, false, true, true); WifiConfiguration configuration1 = new WifiConfiguration(); configuration1.SSID = TEST_SSID; configuration1.BSSID = TEST_BSSID; configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); WifiNetworkSuggestion suggestion1 = - new WifiNetworkSuggestion(configuration1, null, false, true, true); + new WifiNetworkSuggestion(configuration1, null, false, true, true, false); assertEquals(suggestion, suggestion1); assertEquals(suggestion.hashCode(), suggestion1.hashCode()); @@ -616,13 +626,13 @@ public class WifiNetworkSuggestionTest { configuration.SSID = TEST_SSID; configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); WifiNetworkSuggestion suggestion = - new WifiNetworkSuggestion(configuration, null, false, false, true); + new WifiNetworkSuggestion(configuration, null, false, false, true, false); WifiConfiguration configuration1 = new WifiConfiguration(); configuration1.SSID = TEST_SSID_1; configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); WifiNetworkSuggestion suggestion1 = - new WifiNetworkSuggestion(configuration1, null, false, false, true); + new WifiNetworkSuggestion(configuration1, null, false, false, true, false); assertNotEquals(suggestion, suggestion1); } @@ -638,13 +648,13 @@ public class WifiNetworkSuggestionTest { configuration.BSSID = TEST_BSSID; configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); WifiNetworkSuggestion suggestion = - new WifiNetworkSuggestion(configuration, null, false, false, true); + new WifiNetworkSuggestion(configuration, null, false, false, true, true); WifiConfiguration configuration1 = new WifiConfiguration(); configuration1.SSID = TEST_SSID; configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); WifiNetworkSuggestion suggestion1 = - new WifiNetworkSuggestion(configuration1, null, false, false, true); + new WifiNetworkSuggestion(configuration1, null, false, false, true, true); assertNotEquals(suggestion, suggestion1); } @@ -659,13 +669,13 @@ public class WifiNetworkSuggestionTest { configuration.SSID = TEST_SSID; configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); WifiNetworkSuggestion suggestion = - new WifiNetworkSuggestion(configuration, null, false, false, true); + new WifiNetworkSuggestion(configuration, null, false, false, true, true); WifiConfiguration configuration1 = new WifiConfiguration(); configuration1.SSID = TEST_SSID; configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); WifiNetworkSuggestion suggestion1 = - new WifiNetworkSuggestion(configuration1, null, false, false, true); + new WifiNetworkSuggestion(configuration1, null, false, false, true, true); assertNotEquals(suggestion, suggestion1); } @@ -714,9 +724,38 @@ public class WifiNetworkSuggestionTest { */ @Test(expected = IllegalStateException.class) public void testSetIsUserAllowedToManuallyConnectToWithOpenNetwork() { - WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder() + new WifiNetworkSuggestion.Builder() .setSsid(TEST_SSID) .setCredentialSharedWithUser(true) .build(); } + + /** + * Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception + * when {@link WifiNetworkSuggestion.Builder#setIsInitialAutoJoinEnabled(boolean)} to + * false on a open network suggestion. + */ + @Test(expected = IllegalStateException.class) + public void testSetIsAutoJoinDisabledWithOpenNetwork() { + new WifiNetworkSuggestion.Builder() + .setSsid(TEST_SSID) + .setIsInitialAutoJoinEnabled(false) + .build(); + } + + /** + * Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception + * when set both {@link WifiNetworkSuggestion.Builder#setIsInitialAutoJoinEnabled(boolean)} + * and {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} (boolean)} + * to false on a network suggestion. + */ + @Test(expected = IllegalStateException.class) + public void testSetIsAutoJoinDisabledWithSecureNetworkNotSharedWithUser() { + new WifiNetworkSuggestion.Builder() + .setSsid(TEST_SSID) + .setWpa2Passphrase(TEST_PRESHARED_KEY) + .setCredentialSharedWithUser(false) + .setIsInitialAutoJoinEnabled(false) + .build(); + } } diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java index 94054fdde8da..603e78b90ff2 100644 --- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java +++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java @@ -172,6 +172,7 @@ public class PasspointConfigurationTest { assertFalse(config.validate()); assertFalse(config.validateForR2()); assertTrue(config.isAutoJoinEnabled()); + assertTrue(config.isMacRandomizationEnabled()); } /** |