diff options
357 files changed, 17286 insertions, 3945 deletions
diff --git a/Android.bp b/Android.bp index a7ac094e1b56..4c983be1d5de 100644 --- a/Android.bp +++ b/Android.bp @@ -268,6 +268,7 @@ filegroup { ":framework-tethering-srcs", ":updatable-media-srcs", ":framework-mediaprovider-sources", + ":framework-permission-sources", ":framework-wifi-updatable-sources", ":ike-srcs", ] @@ -409,6 +410,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", ], @@ -437,8 +439,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", ], @@ -464,6 +467,7 @@ 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/wifi", "//frameworks/opt/net/wifi/service", @@ -488,6 +492,7 @@ java_library { "updatable_media_stubs", "framework_mediaprovider_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", @@ -637,6 +642,7 @@ 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", @@ -1124,6 +1130,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", 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 58bb6056db98..83195dc73db6 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java @@ -18,6 +18,7 @@ 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; @@ -25,6 +26,7 @@ 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; @@ -95,4 +97,34 @@ public class AppSearchManager { } 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/IAppSearchManager.aidl b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl index 8085aa8b006c..fc83d8ccbd4a 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl +++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl @@ -28,4 +28,5 @@ interface IAppSearchManager { * if setSchema fails. */ void setSchema(in byte[] schemaProto, in AndroidFuture callback); + void put(in byte[] documentBytes, in AndroidFuture callback); } diff --git a/apex/appsearch/service/Android.bp b/apex/appsearch/service/Android.bp index 73272317b5f7..04f385e8c6f6 100644 --- a/apex/appsearch/service/Android.bp +++ b/apex/appsearch/service/Android.bp @@ -20,6 +20,5 @@ java_library { "framework-appsearch", "services.core", ], - static_libs: ["icing-java-proto-lite"], 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 96316b311912..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,9 +17,14 @@ 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; @@ -40,9 +45,27 @@ 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); - throw new UnsupportedOperationException("setSchema not yet implemented: " + schema); + 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/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/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/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index f6512a67ff15..102e8485aac5 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -257,7 +257,7 @@ public class JobSchedulerService extends com.android.server.SystemService private final List<JobRestriction> mJobRestrictions; private final CountQuotaTracker mQuotaTracker; - private static final String QUOTA_TRACKER_SCHEDULE_TAG = ".schedule()"; + private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()"; private final PlatformCompat mPlatformCompat; /** @@ -522,7 +522,7 @@ public class JobSchedulerService extends com.android.server.SystemService 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 = 500; + 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; @@ -740,6 +740,8 @@ public class JobSchedulerService extends com.android.server.SystemService 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( @@ -1054,44 +1056,48 @@ public class JobSchedulerService extends com.android.server.SystemService public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName, int userId, String tag) { - final String pkg = packageName == null ? job.getService().getPackageName() : packageName; - if (!mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_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; + 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); } - } 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"); } - 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; } - return JobScheduler.RESULT_FAILURE; + mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG); } - mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_SCHEDULE_TAG); try { if (ActivityManager.getService().isAppStartModeDisabled(uId, 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 adc16d25a1b9..6444b4e7c9ad 100644 --- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java +++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java @@ -744,23 +744,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { 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 pullWifiActivityInfo( int tagId, long elapsedNanos, long wallClockNanos, List<StatsLogEventWrapper> pulledData) { @@ -1421,21 +1404,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } - 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(); @@ -2027,11 +1995,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { long wallClockNanos = SystemClock.currentTimeMicro() * 1000L; switch (tagId) { - case StatsLog.KERNEL_WAKELOCK: { - pullKernelWakelock(tagId, elapsedNanos, wallClockNanos, ret); - break; - } - case StatsLog.WIFI_ACTIVITY_INFO: { pullWifiActivityInfo(tagId, elapsedNanos, wallClockNanos, ret); break; @@ -2135,11 +2098,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { break; } - case StatsLog.BUILD_INFORMATION: { - pullBuildInformation(tagId, elapsedNanos, wallClockNanos, ret); - break; - } - case StatsLog.PROCESS_CPU_TIME: { pullProcessCpuTime(tagId, elapsedNanos, wallClockNanos, ret); break; diff --git a/api/current.txt b/api/current.txt index 16786ffe701a..d5ece2e7301c 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6839,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); @@ -10404,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"; @@ -10629,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"; @@ -11788,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(); @@ -29651,7 +29655,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); } @@ -29750,6 +29754,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(); @@ -30507,6 +30524,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); @@ -30632,6 +30654,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(); @@ -39044,7 +39067,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 @@ -44061,6 +44084,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(); @@ -44144,6 +44168,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(); @@ -45153,6 +45178,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); @@ -45846,6 +45904,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); @@ -45863,6 +45922,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 @@ -46461,6 +46521,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 diff --git a/api/system-current.txt b/api/system-current.txt index 056fb08c9220..d9d9a4d1377f 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -242,8 +242,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 } @@ -807,7 +809,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); @@ -5194,6 +5195,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(); @@ -8011,6 +8016,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"; @@ -8068,6 +8075,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(); @@ -8093,6 +8101,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"; @@ -8105,13 +8114,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"; } @@ -9463,6 +9480,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(); @@ -10344,7 +10366,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); @@ -10502,6 +10526,7 @@ package android.telephony { } 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); } @@ -10722,6 +10747,7 @@ package android.telephony { 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"; @@ -10761,6 +10787,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 @@ -10784,6 +10811,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); @@ -12116,7 +12144,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); } @@ -12278,6 +12327,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(); } @@ -12412,6 +12467,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 10028f20aad3..4e94ef27134f 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -3137,6 +3137,15 @@ 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); @@ -3267,6 +3276,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); @@ -4662,6 +4672,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/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/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/atoms.proto b/cmds/statsd/src/atoms.proto index cbece78ec355..0255961b75ec 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -342,7 +342,7 @@ message Atom { } // Pulled events will start at field 10000. - // Next: 10070 + // Next: 10071 oneof pulled { WifiBytesTransfer wifi_bytes_transfer = 10000; WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001; @@ -399,7 +399,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; @@ -413,6 +413,7 @@ message Atom { 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. @@ -6946,11 +6947,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. 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 37fbf393dc4e..b5920cb6057a 100644 --- a/cmds/statsd/src/external/StatsPullerManager.cpp +++ b/cmds/statsd/src/external/StatsPullerManager.cpp @@ -60,10 +60,6 @@ const int64_t NO_ALARM_UPDATE = INT64_MAX; std::map<PullerKey, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = { - // 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()}}, @@ -221,10 +217,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/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/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/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/NotificationChannel.java b/core/java/android/app/NotificationChannel.java index a33c2c19d05c..bdc7b992421a 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -31,6 +31,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 +72,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 @@ -826,8 +827,8 @@ 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)); } @Nullable @@ -1175,7 +1176,7 @@ public final class NotificationChannel implements Parcelable { + ", mImportanceLockedDefaultApp=" + mImportanceLockedDefaultApp + ", mOriginalImp=" + mOriginalImportance + ", mParent=" + mParentId - + ", mConversationId" + mConversationId; + + ", mConversationId=" + mConversationId; } /** @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 a9be9ea33089..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; @@ -693,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/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 8eee73c882e2..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. */ diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index c584575f4839..be8e1d60f290 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -6850,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) { 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/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/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 4f06561a2772..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. * 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/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java index 456ebf23afd5..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; @@ -1676,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 @@ -1697,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 03d29a3855e1..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; diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java index 7fd86f68876a..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)); + } } /** 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/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/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/UserManager.java b/core/java/android/os/UserManager.java index f84e483e10ac..2eaefca0efa3 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -2453,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}. @@ -2464,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/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/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 e2b33e01d45f..4f84183209aa 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5137,7 +5137,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); @@ -8820,9 +8819,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"; /** @@ -9994,24 +9993,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"; @@ -10021,6 +10013,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"; /** @@ -10064,10 +10057,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. @@ -10092,17 +10085,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}. * @@ -10120,6 +10102,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"; /** @@ -10228,18 +10211,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"; @@ -10265,69 +10241,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. @@ -10367,6 +10284,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"; /** 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..c9d3b92b27ff 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 @@ -979,6 +990,20 @@ public class PhoneStateListener { } /** + * 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 + } + + /** * The callback methods need to be called on the handler thread where * this object was created. If the binder did that for us it'd be nice. * @@ -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..4dffa62e6a7d 100644 --- a/core/java/android/telephony/TelephonyRegistryManager.java +++ b/core/java/android/telephony/TelephonyRegistryManager.java @@ -708,4 +708,21 @@ public class TelephonyRegistryManager { } 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/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 9d22d304ad00..c191a0daeb38 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -59,7 +59,6 @@ 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, "false"); 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/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/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/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 2ef944f35982..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) { @@ -1054,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; @@ -2892,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) { @@ -3072,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); @@ -3091,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 &= @@ -4891,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) { @@ -5458,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 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/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/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 f851e10fa4f6..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; @@ -5741,10 +5741,10 @@ public class Editor { 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/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 b6703168264e..457c0331c141 100644 --- a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java +++ b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java @@ -75,11 +75,14 @@ public class AccessibilityButtonChooserActivity extends Activity { 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 final List<AccessibilityButtonTarget> mTargets = new ArrayList<>(); private AlertDialog mAlertDialog; @@ -211,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( @@ -325,11 +333,11 @@ public class AccessibilityButtonChooserActivity extends Activity { return false; } - private void disableWhiteListingService(String componentId) { + 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], /* settingsValueOn */ 1); + WHITE_LISTING_FEATURES[i][SETTINGS_KEY], settingsValue); return; } } @@ -339,7 +347,8 @@ public class AccessibilityButtonChooserActivity extends Activity { final String componentId = componentName.flattenToString(); if (isWhiteListingService(componentId)) { - disableWhiteListingService(componentName.flattenToString()); + setWhiteListingServiceEnabled(componentName.flattenToString(), + /* settingsValueOff */ 0); } else { setAccessibilityServiceState(this, componentName, /* enabled= */ false); } @@ -356,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; } @@ -440,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, @@ -545,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; @@ -580,6 +648,12 @@ 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); @@ -595,8 +669,6 @@ public class AccessibilityButtonChooserActivity extends Activity { ACCESSIBILITY_BUTTON_USER_TYPE, componentName)) { disableService(componentName); } - } else { - throw new IllegalArgumentException("Unsupported shortcut type:" + mShortcutType); } } @@ -605,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); } } @@ -719,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/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/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/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/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_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/public.xml b/core/res/res/values/public.xml index 98608300e17f..36dbcbd53977 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3008,6 +3008,10 @@ <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 f977ea857fc8..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> @@ -5219,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 30dbfc711932..669b41e53ba1 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3235,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"/> @@ -3242,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" /> @@ -3649,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" /> @@ -3816,4 +3818,5 @@ <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 2df6d1ca0e2d..3836d6f4115d 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -84,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/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/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/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/widget/EditorCursorDragTest.java b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java index 8c9b4d071c2d..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; @@ -282,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)); @@ -385,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 aa55e08dbcdc..a0cfb31e390f 100644 --- a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java +++ b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java @@ -19,6 +19,7 @@ package android.widget; import static android.widget.espresso.ContextMenuUtils.assertContextMenuContainsItemDisabled; import static android.widget.espresso.ContextMenuUtils.assertContextMenuContainsItemEnabled; import static android.widget.espresso.ContextMenuUtils.assertContextMenuIsNotDisplayed; +import static android.widget.espresso.DragHandleUtils.assertNoSelectionHandles; import static android.widget.espresso.DragHandleUtils.onHandleView; import static android.widget.espresso.TextViewActions.mouseClick; import static android.widget.espresso.TextViewActions.mouseClickOnTextAtIndex; @@ -47,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; @@ -88,11 +88,6 @@ public class TextViewActivityMouseTest { 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("")); } @@ -193,7 +188,6 @@ public class TextViewActivityMouseTest { } @Test - @Suppress // Consistently failing. b/29591177 public void testDragAndDrop_longClick() { final String text = "abc def ghi."; onView(withId(R.id.textview)).perform(mouseClick()); @@ -395,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/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/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/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/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/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/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index 6f5ba849f8f8..5a72b221ce08 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -119,7 +119,7 @@ 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); @@ -297,11 +297,11 @@ public final class Tuner implements AutoCloseable { * * @param statusTypes an array of status type which the caller request. * - * @return statuses an array of statuses which response the caller's - * request. + * @return statuses which response the caller's requests. * @hide */ - public FrontendStatus[] getFrontendStatus(int[] statusTypes) { + @Nullable + public FrontendStatus getFrontendStatus(int[] statusTypes) { return nativeGetFrontendStatus(statusTypes); } diff --git a/media/java/android/media/tv/tuner/TunerConstants.java b/media/java/android/media/tv/tuner/TunerConstants.java index 4532122034ed..19cfa32eccd6 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; @@ -317,156 +322,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,630 +520,50 @@ 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(prefix = "FRONTEND_DVBS_STANDARD", - value = {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(prefix = "FRONTEND_DVBT_STANDARD", - value = {FRONTEND_DVBT_STANDARD_AUTO, FRONTEND_DVBT_STANDARD_T, - FRONTEND_DVBT_STANDARD_T2} - ) - @Retention(RetentionPolicy.SOURCE) - public @interface FrontendDvbtStandard {} - /** @hide */ - public static final int FRONTEND_DVBT_STANDARD_AUTO = Constants.FrontendDvbtStandard.AUTO; - /** @hide */ - public static final int FRONTEND_DVBT_STANDARD_T = Constants.FrontendDvbtStandard.T; - /** @hide */ - public static final int FRONTEND_DVBT_STANDARD_T2 = Constants.FrontendDvbtStandard.T2; - - /** @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, 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/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 index 8118fcc0e900..5e7d2189706c 100644 --- a/media/java/android/media/tv/tuner/frontend/ScanCallback.java +++ b/media/java/android/media/tv/tuner/frontend/ScanCallback.java @@ -16,8 +16,6 @@ package android.media.tv.tuner.frontend; -import android.media.tv.tuner.TunerConstants; - /** * Scan callback. * @@ -49,10 +47,10 @@ public interface ScanCallback { void onInputStreamIds(int[] inputStreamIds); /** Locked signal standard. */ - void onDvbsStandard(@TunerConstants.FrontendDvbsStandard int dvbsStandandard); + void onDvbsStandard(@DvbsFrontendSettings.Standard int dvbsStandandard); /** Locked signal standard. */ - void onDvbtStandard(@TunerConstants.FrontendDvbtStandard int dvbtStandard); + void onDvbtStandard(@DvbtFrontendSettings.Standard int dvbtStandard); /** PLP status in a tuned frequency band for ATSC3 frontend. */ void onAtsc3PlpInfos(Atsc3PlpInfo[] atsc3PlpInfos); 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..59e1122707a5 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); @@ -228,8 +173,10 @@ public class MediaRouter2Test { @Test public void testRequestCreateSessionWithInvalidArguments() { - MediaRoute2Info route = new MediaRoute2Info.Builder("id", "name").build(); String routeFeature = "routeFeature"; + MediaRoute2Info route = new MediaRoute2Info.Builder("id", "name") + .addFeature(routeFeature) + .build(); // Tests null route assertThrows(NullPointerException.class, 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/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/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/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/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/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/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 1a1a480d59e2..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, diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index df77b5bfb895..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" /> @@ -308,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" @@ -637,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/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 c26cb9a18dd4..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> @@ -1160,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/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/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/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/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/statusbar/notification/collection/ListDumper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java index 73bfe2536830..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,42 +16,135 @@ package com.android.systemui.statusbar.notification.collection; +import java.util.Arrays; import java.util.List; - /** * 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 4b15b7fbce5d..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,6 +47,8 @@ import android.util.ArrayMap; import android.util.Log; import com.android.internal.statusbar.IStatusBarService; +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; @@ -56,6 +58,8 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.No 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; @@ -92,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<>(); @@ -107,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. */ @@ -442,4 +447,19 @@ public class NotifCollection { 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/NotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java index 71245178a876..0377f57a7a9c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java @@ -22,7 +22,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo 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.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; @@ -48,7 +48,7 @@ import javax.inject.Singleton; * 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 #setSectionsProvider} and {@link #setComparators} + * {@link #setSections} and {@link #setComparators} * * The exact order of all hooks is as follows: * 0. Collection listeners are fired ({@link #addCollectionListener}). @@ -58,7 +58,7 @@ import javax.inject.Singleton; * 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. SectionsProvider is called on each top-level entry in the list ({@link #setSectionsProvider}) + * 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}) @@ -142,14 +142,13 @@ public class NotifPipeline { } /** - * 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. + * 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 setSectionsProvider(SectionsProvider provider) { - mShadeListBuilder.setSectionsProvider(provider); + public void setSections(List<NotifSection> sections) { + mShadeListBuilder.setSections(sections); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index 76c524be1b8f..97f8ec5f5bb7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.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,7 +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.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; @@ -39,13 +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; @@ -63,7 +67,7 @@ import javax.inject.Singleton; */ @MainThread @Singleton -public class ShadeListBuilder { +public class ShadeListBuilder implements Dumpable { private final SystemClock mSystemClock; private final NotifLog mNotifLog; @@ -79,7 +83,7 @@ public class ShadeListBuilder { 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<>(); @@ -92,10 +96,14 @@ public class ShadeListBuilder { private final List<ListEntry> mReadOnlyNotifList = Collections.unmodifiableList(mNotifList); @Inject - public ShadeListBuilder(SystemClock systemClock, NotifLog notifLog) { + public ShadeListBuilder( + SystemClock systemClock, + NotifLog notifLog, + DumpController dumpController) { Assert.isMainThread(); mSystemClock = systemClock; mNotifLog = notifLog; + dumpController.registerDumpable(TAG, this); } /** @@ -163,12 +171,15 @@ public class ShadeListBuilder { promoter.setInvalidationListener(this::onPromoterInvalidated); } - 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); + } } void setComparators(List<NotifComparator> comparators) { @@ -223,12 +234,12 @@ public class ShadeListBuilder { 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); @@ -311,7 +322,7 @@ public class ShadeListBuilder { 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(); @@ -324,7 +335,7 @@ public class ShadeListBuilder { // 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); @@ -573,6 +584,8 @@ public class ShadeListBuilder { * 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; @@ -582,11 +595,12 @@ public class ShadeListBuilder { 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); } @@ -747,6 +761,45 @@ public class ShadeListBuilder { 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)) { @@ -772,6 +825,19 @@ public class ShadeListBuilder { } } + @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 { /** @@ -783,16 +849,13 @@ public class ShadeListBuilder { 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/coordinator/Coordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java index c1a11b2f64c8..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 @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator; 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; /** @@ -28,4 +29,8 @@ public interface Coordinator { * Coordinators should register their listeners and {@link Pluggable}s to the pipeline. */ void attach(NotifPipeline pipeline); + + default NotifSection getSection() { + return null; + } } 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 562a618126de..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,9 +16,11 @@ 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.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; @@ -39,18 +41,21 @@ import javax.inject.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); @@ -59,6 +64,13 @@ 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()); + } + } } /** @@ -69,6 +81,8 @@ public class NotifCoordinators implements Dumpable { for (Coordinator c : mCoordinators) { c.attach(pipeline); } + + pipeline.setSections(mOrderedSections); } @Override @@ -78,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/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java index 1ab20a95ca6c..5cd3e9411b08 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java @@ -41,6 +41,9 @@ 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; @@ -49,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; @@ -66,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/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/logging/NotifEvent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java index 02acc8149cc4..2374cde0c16a 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 @@ -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", @@ -170,13 +171,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} 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..ec420f312b06 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java @@ -0,0 +1,633 @@ +/* + * 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_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.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); + }; + + 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); + } + + } + + 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: + // TODO: when demotion status field exists on notificationchannel + 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 4f56f56735a5..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; @@ -175,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; @@ -227,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); } } }; @@ -291,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 ----- 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 ba70cf4a39f4..dc9cf7714e7e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -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/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/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/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java index 09cc5ba204f8..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,6 +49,7 @@ 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; @@ -103,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); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java index be067481b779..e915be37705b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.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,6 +39,7 @@ 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.ShadeListBuilder.OnRenderListListener; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener; @@ -45,7 +48,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo 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; @@ -100,7 +103,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); Assert.sMainLooper = TestableLooper.get(this).getLooper(); - mListBuilder = new ShadeListBuilder(mSystemClock, mNotifLog); + mListBuilder = new ShadeListBuilder(mSystemClock, mNotifLog, mock(DumpController.class)); mListBuilder.setOnRenderListListener(mOnRenderListListener); mListBuilder.attach(mNotifCollection); @@ -448,6 +451,29 @@ public class ShadeListBuilderTest 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)); @@ -550,12 +576,17 @@ public class ShadeListBuilderTest 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); @@ -582,19 +613,93 @@ public class ShadeListBuilderTest 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 @@ -629,7 +734,7 @@ public class ShadeListBuilderTest 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); @@ -637,7 +742,7 @@ public class ShadeListBuilderTest 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); @@ -657,7 +762,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { mOnBeforeTransformGroupsListener, promoter, mOnBeforeSortListener, - sectioner, + section, comparator, preRenderFilter, mOnBeforeRenderListListener, @@ -670,7 +775,7 @@ public class ShadeListBuilderTest 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()) @@ -684,12 +789,12 @@ public class ShadeListBuilderTest 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 @@ -709,7 +814,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { verify(mOnRenderListListener).onRenderList(anyList()); clearInvocations(mOnRenderListListener); - sectionsProvider.invalidateList(); + section.invalidateList(); verify(mOnRenderListListener).onRenderList(anyList()); clearInvocations(mOnRenderListListener); @@ -982,9 +1087,10 @@ public class ShadeListBuilderTest 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()); @@ -999,6 +1105,11 @@ public class ShadeListBuilderTest 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) { @@ -1081,7 +1192,8 @@ public class ShadeListBuilderTest 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); } } @@ -1166,6 +1278,21 @@ public class ShadeListBuilderTest 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; @@ -1202,25 +1329,18 @@ public class ShadeListBuilderTest 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/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java new file mode 100644 index 000000000000..0bf458cac618 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java @@ -0,0 +1,816 @@ +/* + * 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.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 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/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/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/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 36a9e0ebbf5d..c9fdd5ae407a 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -1147,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)); } 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/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 7d354d20cb67..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; @@ -269,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. */ @@ -690,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 " @@ -1307,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) { @@ -2322,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. @@ -2342,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; } /** @@ -2355,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) { @@ -2363,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 @@ -2414,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=" @@ -2577,7 +2604,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return; } - requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags); + if (requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags)) { + return; + } if (isSameViewEntered) { return; @@ -3678,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/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/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/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..396b9772fd12 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -2855,8 +2855,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/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/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/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/integrity/IntegrityFileManager.java b/services/core/java/com/android/server/integrity/IntegrityFileManager.java index 3befa6edd6cb..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; } } 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 da778aa696a4..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,6 +16,8 @@ package com.android.server.integrity.model; +import static com.android.server.integrity.model.ComponentBitSize.BYTE_BITS; + import java.io.IOException; import java.io.OutputStream; import java.util.Arrays; @@ -24,7 +26,6 @@ import java.util.Arrays; public class BitOutputStream { private static final int BUFFER_SIZE = 4 * 1024; - private static final int BYTE_BITS = 8; private int mNextBitIndex; 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 4bf8fe8d93b6..000000000000 --- a/services/core/java/com/android/server/integrity/model/BitTrackedInputStream.java +++ /dev/null @@ -1,76 +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 int mReadBitsCount; - - /** Constructor with byte array. */ - public BitTrackedInputStream(byte[] inputStream) { - super(inputStream); - mReadBitsCount = 0; - } - - /** Constructor with input stream. */ - public BitTrackedInputStream(InputStream inputStream) { - super(inputStream); - mReadBitsCount = 0; - } - - /** Obtains an integer value of the next {@code numOfBits}. */ - @Override - public int getNext(int numOfBits) throws IOException { - mReadBitsCount += numOfBits; - return super.getNext(numOfBits); - } - - /** Returns the current cursor position showing the number of bits that are read. */ - public int getReadBitsCount() { - return mReadBitsCount; - } - - /** - * Returns true if we can read more rules by checking whether the end index is not reached yet. - */ - public boolean canReadMoreRules(int endIndexBytes) { - return mReadBitsCount < endIndexBytes * 8; - } - - /** - * 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 - mReadBitsCount; - if (bitCountToRead < 0) { - throw new IllegalStateException("The byte position is already read."); - } - super.getNext(bitCountToRead); - mReadBitsCount = 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 0d6807a2fd8b..ceed054e4a53 100644 --- a/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java +++ b/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java @@ -27,8 +27,6 @@ import java.io.OutputStream; */ public class ByteTrackedOutputStream extends OutputStream { - private static final int INT_BYTES = 4; - private int mWrittenBytesCount; private final OutputStream mOutputStream; @@ -39,7 +37,7 @@ public class ByteTrackedOutputStream extends OutputStream { @Override public void write(int b) throws IOException { - mWrittenBytesCount += INT_BYTES; + mWrittenBytesCount++; mOutputStream.write(b); } @@ -49,8 +47,7 @@ public class ByteTrackedOutputStream extends OutputStream { */ @Override public void write(byte[] bytes) throws IOException { - mWrittenBytesCount += bytes.length; - mOutputStream.write(bytes); + write(bytes, 0, bytes.length); } @Override 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..d21febbef9b9 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 250 is in the + // optimal range of efficient computation. + public static final int INDEXING_BLOCK_SIZE = 250; 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 2f285631b982..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,18 +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.canReadMoreRules(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)); } } } @@ -115,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: @@ -141,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 453fa5d84053..595a035baf1d 100644 --- a/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java +++ b/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java @@ -47,4 +47,9 @@ public class RuleIndexRange { 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/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/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/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index d940e3587794..161afb51cbfb 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -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 { diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index b7aa484a3fc7..c80a898184bc 100644 --- a/services/core/java/com/android/server/media/MediaRouterService.java +++ b/services/core/java/com/android/server/media/MediaRouterService.java @@ -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/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/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index b291a2efda8b..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; @@ -5281,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 3e760962da87..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--) { @@ -632,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/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 13efdcdb8561..d0f91c206a2c 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1534,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(); @@ -3303,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); @@ -3315,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 @@ -3745,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); @@ -5467,7 +5478,7 @@ public class PackageManagerService extends IPackageManager.Stub public @NonNull String getServicesSystemSharedLibraryPackageName() { // allow instant applications synchronized (mLock) { - return mServicesSystemSharedLibraryPackageName; + return mServicesExtensionPackageName; } } @@ -22290,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/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/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/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/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index eefcde6bd355..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()); @@ -409,7 +413,6 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { CountDownLatch latch = new CountDownLatch(1); getHandler().post(() -> { - updateRollbackLifetimeDurationInMillis(); synchronized (mLock) { mRollbacks.clear(); mRollbacks.addAll(mRollbackStore.loadRollbacks()); @@ -520,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<>(); diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java index b9ef7b39c451..7a8ddd48611e 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, ""); } @@ -245,12 +245,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 +260,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,15 +274,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, ""); } 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, ""); @@ -362,12 +366,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); } } diff --git a/services/core/java/com/android/server/stats/StatsPullAtomService.java b/services/core/java/com/android/server/stats/StatsPullAtomService.java index 9290381dad56..75fbf01b1d3c 100644 --- a/services/core/java/com/android/server/stats/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/StatsPullAtomService.java @@ -19,6 +19,7 @@ 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.Debug.getIonHeapsSizeKb; import static android.os.Process.THREAD_PRIORITY_BACKGROUND; import static android.os.Process.getUidForPid; import static android.os.storage.VolumeInfo.TYPE_PRIVATE; @@ -263,6 +264,7 @@ public class StatsPullAtomService extends SystemService { registerProcessMemoryHighWaterMark(); registerProcessMemorySnapshot(); registerSystemIonHeapSize(); + registerIonHeapSize(); registerProcessSystemIonHeapSize(); registerTemperature(); registerCoolingDevice(); @@ -622,12 +624,35 @@ public class StatsPullAtomService extends SystemService { return StatsManager.PULL_SUCCESS; } + private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader(); + private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats(); + private void registerKernelWakelock() { - // No op. + int tagId = StatsLog.KERNEL_WAKELOCK; + mStatsManager.registerPullAtomCallback( + tagId, + /* PullAtomMetadata */ null, + (atomTag, data) -> pullKernelWakelock(atomTag, data), + Executors.newSingleThreadExecutor() + ); } - private void pullKernelWakelock() { - // No op. + 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; @@ -931,6 +956,26 @@ public class StatsPullAtomService extends SystemService { // No op. } + 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() { // No op. } @@ -1131,11 +1176,30 @@ public class StatsPullAtomService extends SystemService { } private void registerBuildInformation() { - // No op. + int tagId = StatsLog.BUILD_INFORMATION; + mStatsManager.registerPullAtomCallback( + tagId, + null, // use default PullAtomMetadata values + (atomTag, data) -> pullBuildInformation(atomTag, data), + BackgroundThread.getExecutor() + ); } - private void pullBuildInformation() { - // No op. + 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() { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index c9a702f002de..19bc560dd838 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -2137,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() { @@ -2489,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. 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/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 3092a08099e1..e308f6b8f3ce 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -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; /** @@ -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/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 c38868a2af84..3b2d51985a39 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -247,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(); @@ -851,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. */ 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/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 485899ef05e3..c55acab223d9 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -7456,8 +7456,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)); @@ -7478,7 +7477,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return false; } Objects.requireNonNull(who, "ComponentName is null"); - enforceDeviceOwnerOrProfileOwnerOnUser0OrProfileOwnerOrganizationOwned(); + enforceProfileOwnerOnUser0OrProfileOwnerOrganizationOwned(); return mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) > 0; } @@ -7492,8 +7491,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)); @@ -7514,7 +7512,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; } @@ -8724,7 +8722,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!mHasFeature) { return false; } - enforceManageUsers(); return mInjector.binderWithCleanCallingIdentity(() -> { for (UserInfo ui : mUserManager.getUsers()) { @@ -9061,23 +9058,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"); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index ea2385fd61c5..3dee913ac035 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; @@ -443,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 @@ -555,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); } @@ -754,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"); @@ -791,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"); @@ -808,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. 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/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/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/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/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/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index d6703613d3fc..2cb71039199a 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -3832,11 +3832,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()); 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 4fa73028ece1..000000000000 --- a/services/tests/servicestests/src/com/android/server/integrity/model/BitTrackedInputStreamTest.java +++ /dev/null @@ -1,162 +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_canReadMoreRules() 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.canReadMoreRules(2)).isTrue(); - - // Read until the string parameter. - String stringValue = BinaryFileOperations.getStringValue(bitTrackedInputStream); - - // Verify that the read bytes are counted. - assertThat(stringValue).isEqualTo(packageName); - assertThat(bitTrackedInputStream.canReadMoreRules(2)).isFalse(); - } - - @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/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/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java index 3d40637f22e2..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; @@ -1913,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/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..7ac45f027327 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -354,6 +354,7 @@ 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"); mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true); mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg2, true); @@ -2822,8 +2823,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/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/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/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/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/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/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/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java index 5e2e554b7764..0cfb8c56320a 100644 --- a/telephony/java/android/telephony/PreciseDataConnectionState.java +++ b/telephony/java/android/telephony/PreciseDataConnectionState.java @@ -138,7 +138,7 @@ public final class PreciseDataConnectionState implements Parcelable { * To check the SDK version for {@link PreciseDataConnectionState#getDataConnectionState}. */ @ChangeId - @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R) + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) private static final long GET_DATA_CONNECTION_STATE_CODE_CHANGE = 147600208L; /** 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/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java index 40a7619a3ee3..eefbd44ad6de 100644 --- a/telephony/java/android/telephony/SmsMessage.java +++ b/telephony/java/android/telephony/SmsMessage.java @@ -332,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 * diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 7ee68ea2881c..13aad7eee7b0 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -2276,6 +2276,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. @@ -5449,7 +5459,7 @@ public class TelephonyManager { * To check the SDK version for {@link TelephonyManager#getDataState}. */ @ChangeId - @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R) + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) private static final long GET_DATA_STATE_CODE_CHANGE = 147600208L; /** @@ -5535,7 +5545,7 @@ public class TelephonyManager { * To check the SDK version for {@link TelephonyManager#listen}. */ @ChangeId - @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.P) private static final long LISTEN_CODE_CHANGE = 147600208L; /** @@ -7649,6 +7659,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. * @@ -10685,6 +10707,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 @@ -10711,6 +10734,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 @@ -10737,6 +10761,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 ab22d46df526..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; @@ -382,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()); @@ -618,26 +612,6 @@ public class ProvisioningManager { } - 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); - } catch (RemoteException e) { - // For some reason package manger is not available.. This will fail internally anyways, - // so do not throw error and allow. - } - return true; - } - private static ITelephony getITelephony() { ITelephony binder = ITelephony.Stub.asInterface( TelephonyFrameworkInitializer 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/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/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/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/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/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/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/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/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/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java index d4fd90338885..a9621fc6fb34 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -373,7 +373,6 @@ public class WifiConfiguration implements Parcelable { * ECDHE_ECDSA * ECDHE_RSA * </pre> - * @hide */ public static class SuiteBCipher { private SuiteBCipher() { } diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java index 51b15afec3ab..7cd00b9dbb56 100644 --- a/wifi/java/android/net/wifi/WifiInfo.java +++ b/wifi/java/android/net/wifi/WifiInfo.java @@ -700,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; @@ -712,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..95401032219d 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}. */ 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; |