diff options
438 files changed, 18766 insertions, 3995 deletions
diff --git a/Android.bp b/Android.bp index 58f6119c2243..3ecb5a9340d2 100644 --- a/Android.bp +++ b/Android.bp @@ -297,6 +297,8 @@ filegroup { ], } +// AIDL files under these paths are mixture of public and private ones. +// They shouldn't be exported across module boundaries. java_defaults { name: "framework-aidl-export-defaults", aidl: { @@ -321,12 +323,6 @@ java_defaults { "wifi/aidl-export", ], }, - - required: [ - // TODO: remove gps_debug and protolog.conf.json when the build system propagates "required" properly. - "gps_debug.conf", - "protolog.conf.json.gz", - ], } // Collection of classes that are generated from non-Java files that are not listed in @@ -416,6 +412,12 @@ java_defaults { "view-inspector-annotation-processor", "staledataclass-annotation-processor", ], + + required: [ + // TODO: remove gps_debug and protolog.conf.json when the build system propagates "required" properly. + "gps_debug.conf", + "protolog.conf.json.gz", + ], } filegroup { @@ -539,7 +541,6 @@ java_library { java_library { name: "framework-annotation-proc", - defaults: ["framework-aidl-export-defaults"], srcs: [":framework-all-sources"], libs: [ "app-compat-annotations", @@ -713,7 +714,10 @@ filegroup { name: "framework-tethering-annotations", srcs: [ "core/java/android/annotation/NonNull.java", + "core/java/android/annotation/Nullable.java", + "core/java/android/annotation/RequiresPermission.java", "core/java/android/annotation/SystemApi.java", + "core/java/android/annotation/TestApi.java", ], } // Build ext.jar diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearch.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearch.java index fd201866e702..8bf13eec6249 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearch.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearch.java @@ -101,6 +101,54 @@ public final class AppSearch { this(document.mProto, document.mPropertyBundle); } + /** @hide */ + Document(@NonNull DocumentProto documentProto) { + this(documentProto, new Bundle()); + for (int i = 0; i < documentProto.getPropertiesCount(); i++) { + PropertyProto property = documentProto.getProperties(i); + String name = property.getName(); + if (property.getStringValuesCount() > 0) { + String[] values = new String[property.getStringValuesCount()]; + for (int j = 0; j < values.length; j++) { + values[j] = property.getStringValues(j); + } + mPropertyBundle.putStringArray(name, values); + } else if (property.getInt64ValuesCount() > 0) { + long[] values = new long[property.getInt64ValuesCount()]; + for (int j = 0; j < values.length; j++) { + values[j] = property.getInt64Values(j); + } + mPropertyBundle.putLongArray(property.getName(), values); + } else if (property.getDoubleValuesCount() > 0) { + double[] values = new double[property.getDoubleValuesCount()]; + for (int j = 0; j < values.length; j++) { + values[j] = property.getDoubleValues(j); + } + mPropertyBundle.putDoubleArray(property.getName(), values); + } else if (property.getBooleanValuesCount() > 0) { + boolean[] values = new boolean[property.getBooleanValuesCount()]; + for (int j = 0; j < values.length; j++) { + values[j] = property.getBooleanValues(j); + } + mPropertyBundle.putBooleanArray(property.getName(), values); + } else if (property.getBytesValuesCount() > 0) { + byte[][] values = new byte[property.getBytesValuesCount()][]; + for (int j = 0; j < values.length; j++) { + values[j] = property.getBytesValues(j).toByteArray(); + } + mPropertyBundle.putObject(name, values); + } else if (property.getDocumentValuesCount() > 0) { + Document[] values = new Document[property.getDocumentValuesCount()]; + for (int j = 0; j < values.length; j++) { + values[j] = new Document(property.getDocumentValues(j)); + } + mPropertyBundle.putObject(name, values); + } else { + throw new IllegalStateException("Unknown type of value: " + name); + } + } + } + /** * Creates a new {@link Document.Builder}. * diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java index e3f6b3de433a..15c336820df7 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java @@ -26,6 +26,7 @@ import com.android.internal.infra.AndroidFuture; import com.google.android.icing.proto.SchemaProto; import com.google.android.icing.proto.SearchResultProto; +import com.google.android.icing.proto.SearchSpecProto; import com.google.android.icing.proto.StatusProto; import com.google.android.icing.protobuf.InvalidProtocolBufferException; @@ -186,28 +187,28 @@ public class AppSearchManager { *<p>Currently we support following features in the raw query format: * <ul> * <li>AND - * AND joins (e.g. “match documents that have both the terms ‘dog’ and + * <p>AND joins (e.g. “match documents that have both the terms ‘dog’ and * ‘cat’”). * Example: hello world matches documents that have both ‘hello’ and ‘world’ * <li>OR - * OR joins (e.g. “match documents that have either the term ‘dog’ or + * <p>OR joins (e.g. “match documents that have either the term ‘dog’ or * ‘cat’”). * Example: dog OR puppy * <li>Exclusion - * Exclude a term (e.g. “match documents that do + * <p>Exclude a term (e.g. “match documents that do * not have the term ‘dog’”). * Example: -dog excludes the term ‘dog’ * <li>Grouping terms - * Allow for conceptual grouping of subqueries to enable hierarchical structures (e.g. + * <p>Allow for conceptual grouping of subqueries to enable hierarchical structures (e.g. * “match documents that have either ‘dog’ or ‘puppy’, and either ‘cat’ or ‘kitten’”). * Example: (dog puppy) (cat kitten) two one group containing two terms. * <li>Property restricts - * which properties of a document to specifically match terms in (e.g. + * <p> Specifies which properties of a document to specifically match terms in (e.g. * “match documents where the ‘subject’ property contains ‘important’”). * Example: subject:important matches documents with the term ‘important’ in the * ‘subject’ property * <li>Schema type restricts - * This is similar to property restricts, but allows for restricts on top-level document + * <p>This is similar to property restricts, but allows for restricts on top-level document * fields, such as schema_type. Clients should be able to limit their query to documents of * a certain schema_type (e.g. “match documents that are of the ‘Email’ schema_type”). * Example: { schema_type_filters: “Email”, “Video”,query: “dog” } will match documents @@ -263,7 +264,11 @@ public class AppSearchManager { }, executor); try { - mService.query(queryExpression, searchSpec.getProto().toByteArray(), future); + SearchSpecProto searchSpecProto = searchSpec.getSearchSpecProto(); + searchSpecProto = searchSpecProto.toBuilder().setQuery(queryExpression).build(); + mService.query(searchSpecProto.toByteArray(), + searchSpec.getResultSpecProto().toByteArray(), + searchSpec.getScoringSpecProto().toByteArray(), future); } catch (RemoteException e) { future.completeExceptionally(e); } diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl index 20c8af985c21..eef41ed7104d 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl +++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl @@ -47,12 +47,14 @@ interface IAppSearchManager { void putDocuments(in List documentsBytes, in AndroidFuture<AppSearchBatchResult> callback); /** - * Searches a document based on a given query string. + * Searches a document based on a given specifications. * - * @param queryExpression Query String to search. - * @param searchSpec Serialized SearchSpecProto. + * @param searchSpecBytes Serialized SearchSpecProto. + * @param resultSpecBytes Serialized SearchResultsProto. + * @param scoringSpecBytes Serialized ScoringSpecProto. * @param callback {@link AndroidFuture}. Will be completed with a serialized * {@link SearchResultsProto}, or completed exceptionally if query fails. */ - void query(in String queryExpression, in byte[] searchSpecBytes, in AndroidFuture callback); + void query(in byte[] searchSpecBytes, in byte[] resultSpecBytes, + in byte[] scoringSpecBytes, in AndroidFuture callback); } diff --git a/apex/appsearch/framework/java/android/app/appsearch/MatchInfo.java b/apex/appsearch/framework/java/android/app/appsearch/MatchInfo.java new file mode 100644 index 000000000000..6aa91a3fe9e4 --- /dev/null +++ b/apex/appsearch/framework/java/android/app/appsearch/MatchInfo.java @@ -0,0 +1,182 @@ +/* + * 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.NonNull; +import android.util.Range; + +import com.google.android.icing.proto.SnippetMatchProto; + +/** + * Snippet: It refers to a substring of text from the content of document that is returned as a + * part of search result. + * This class represents a match objects for any Snippets that might be present in + * {@link SearchResults} from query. Using this class user can get the full text, exact matches and + * Snippets of document content for a given match. + * + * <p>Class Example 1: + * A document contains following text in property subject: + * <p>A commonly used fake word is foo. Another nonsense word that’s used a lot is bar. + * + * <p>If the queryExpression is "foo". + * + * <p>{@link MatchInfo#getPropertyPath()} returns "subject" + * <p>{@link MatchInfo#getFullText()} returns "A commonly used fake word is foo. Another nonsense + * word that’s used a lot is bar." + * <p>{@link MatchInfo#getExactMatchPosition()} returns [29, 32] + * <p>{@link MatchInfo#getExactMatch()} returns "foo" + * <p>{@link MatchInfo#getSnippetPosition()} returns [29, 41] + * <p>{@link MatchInfo#getSnippet()} returns "is foo. Another" + * <p> + * <p>Class Example 2: + * A document contains a property name sender which contains 2 property names name and email, so + * we will have 2 property paths: {@code sender.name} and {@code sender.email}. + * <p> Let {@code sender.name = "Test Name Jr."} and {@code sender.email = "TestNameJr@gmail.com"} + * + * <p>If the queryExpression is "Test". We will have 2 matches. + * + * <p> Match-1 + * <p>{@link MatchInfo#getPropertyPath()} returns "sender.name" + * <p>{@link MatchInfo#getFullText()} returns "Test Name Jr." + * <p>{@link MatchInfo#getExactMatchPosition()} returns [0, 4] + * <p>{@link MatchInfo#getExactMatch()} returns "Test" + * <p>{@link MatchInfo#getSnippetPosition()} returns [0, 9] + * <p>{@link MatchInfo#getSnippet()} returns "Test Name Jr." + * <p> Match-2 + * <p>{@link MatchInfo#getPropertyPath()} returns "sender.email" + * <p>{@link MatchInfo#getFullText()} returns "TestNameJr@gmail.com" + * <p>{@link MatchInfo#getExactMatchPosition()} returns [0, 20] + * <p>{@link MatchInfo#getExactMatch()} returns "TestNameJr@gmail.com" + * <p>{@link MatchInfo#getSnippetPosition()} returns [0, 20] + * <p>{@link MatchInfo#getSnippet()} returns "TestNameJr@gmail.com" + * @hide + */ +// TODO(sidchhabra): Capture real snippet after integration with icingLib. +public final class MatchInfo { + + private final String mPropertyPath; + private final SnippetMatchProto mSnippetMatch; + private final AppSearch.Document mDocument; + /** + * List of content with same property path in a document when there are multiple matches in + * repeated sections. + */ + private final String[] mValues; + + /** @hide */ + public MatchInfo(@NonNull String propertyPath, @NonNull SnippetMatchProto snippetMatch, + @NonNull AppSearch.Document document) { + mPropertyPath = propertyPath; + mSnippetMatch = snippetMatch; + mDocument = document; + // In IcingLib snippeting is available for only 3 data types i.e String, double and long, + // so we need to check which of these three are requested. + // TODO (sidchhabra): getPropertyStringArray takes property name, handle for property path. + String[] values = mDocument.getPropertyStringArray(propertyPath); + if (values == null) { + values = doubleToString(mDocument.getPropertyDoubleArray(propertyPath)); + } + if (values == null) { + values = longToString(mDocument.getPropertyLongArray(propertyPath)); + } + if (values == null) { + throw new IllegalStateException("No content found for requested property path!"); + } + mValues = values; + } + + /** + * Gets the property path corresponding to the given entry. + * <p>Property Path: '.' - delimited sequence of property names indicating which property in + * the Document these snippets correspond to. + * <p>Example properties: 'body', 'sender.name', 'sender.emailaddress', etc. + * For class example 1 this returns "subject" + */ + @NonNull + public String getPropertyPath() { + return mPropertyPath; + } + + /** + * Gets the full text corresponding to the given entry. + * <p>For class example this returns "A commonly used fake word is foo. Another nonsense word + * that’s used a lot is bar." + */ + @NonNull + public String getFullText() { + return mValues[mSnippetMatch.getValuesIndex()]; + } + + /** + * Gets the exact match range corresponding to the given entry. + * <p>For class example 1 this returns [29, 32] + */ + @NonNull + public Range getExactMatchPosition() { + return new Range(mSnippetMatch.getExactMatchPosition(), + mSnippetMatch.getExactMatchPosition() + mSnippetMatch.getExactMatchBytes()); + } + + /** + * Gets the exact match corresponding to the given entry. + * <p>For class example 1 this returns "foo" + */ + @NonNull + public CharSequence getExactMatch() { + return getSubstring(getExactMatchPosition()); + } + + /** + * Gets the snippet range corresponding to the given entry. + * <p>For class example 1 this returns [29, 41] + */ + @NonNull + public Range getSnippetPosition() { + return new Range(mSnippetMatch.getWindowPosition(), + mSnippetMatch.getWindowPosition() + mSnippetMatch.getWindowBytes()); + } + + /** + * Gets the snippet corresponding to the given entry. + * <p>Snippet - Provides a subset of the content to display. The + * length of this content can be changed {@link SearchSpec.Builder#setMaxSnippetSize(int)}. + * Windowing is centered around the middle of the matched token with content on either side + * clipped to token boundaries. + * <p>For class example 1 this returns "foo. Another" + */ + @NonNull + public CharSequence getSnippet() { + return getSubstring(getSnippetPosition()); + } + + private CharSequence getSubstring(Range range) { + return getFullText() + .substring((int) range.getLower(), (int) range.getUpper()); + } + + /** Utility method to convert double[] to String[] */ + private String[] doubleToString(double[] values) { + //TODO(sidchhabra): Implement the method. + return null; + } + + /** Utility method to convert long[] to String[] */ + private String[] longToString(long[] values) { + //TODO(sidchhabra): Implement the method. + return null; + } +} diff --git a/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java b/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java index d763103f1217..f48ebde288f3 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java +++ b/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java @@ -17,27 +17,51 @@ package android.app.appsearch; import android.annotation.NonNull; +import android.annotation.Nullable; import com.google.android.icing.proto.SearchResultProto; +import com.google.android.icing.proto.SnippetMatchProto; +import com.google.android.icing.proto.SnippetProto; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.NoSuchElementException; /** * SearchResults are a list of results that are returned from a query. Each result from this * list contains a document and may contain other fields like snippets based on request. + * This iterator class is not thread safe. * @hide */ -public final class SearchResults { +public final class SearchResults implements Iterator<SearchResults.Result> { private final SearchResultProto mSearchResultProto; + private int mNextIdx; /** @hide */ public SearchResults(SearchResultProto searchResultProto) { mSearchResultProto = searchResultProto; } + @Override + public boolean hasNext() { + return mNextIdx < mSearchResultProto.getResultsCount(); + } + + @NonNull + @Override + public Result next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + Result result = new Result(mSearchResultProto.getResults(mNextIdx)); + mNextIdx++; + return result; + } + + + /** * This class represents the result obtained from the query. It will contain the document which * which matched the specified query string and specifications. @@ -46,6 +70,9 @@ public final class SearchResults { public static final class Result { private final SearchResultProto.ResultProto mResultProto; + @Nullable + private AppSearch.Document mDocument; + private Result(SearchResultProto.ResultProto resultProto) { mResultProto = resultProto; } @@ -55,35 +82,47 @@ public final class SearchResults { * @return Document object which matched the query. * @hide */ - // TODO(sidchhabra): Switch to Document constructor that takes proto. @NonNull public AppSearch.Document getDocument() { - return AppSearch.Document.newBuilder(mResultProto.getDocument().getUri(), - mResultProto.getDocument().getSchema()) - .setCreationTimestampMillis(mResultProto.getDocument().getCreationTimestampMs()) - .setScore(mResultProto.getDocument().getScore()) - .build(); + if (mDocument == null) { + mDocument = new AppSearch.Document(mResultProto.getDocument()); + } + return mDocument; } - // TODO(sidchhabra): Add Getter for ResultReader for Snippet. + /** + * Contains a list of Snippets that matched the request. Only populated when requested in + * {@link SearchSpec.Builder#setMaxSnippetSize(int)}. + * @return List of matches based on {@link SearchSpec}, if snippeting is disabled and this + * method is called it will return {@code null}. Users can also restrict snippet population + * using {@link SearchSpec.Builder#setNumToSnippet} and + * {@link SearchSpec.Builder#setNumMatchesPerProperty}, for all results after that value + * this method will return {@code null}. + * @hide + */ + // TODO(sidchhabra): Replace Document with proper constructor. + @Nullable + public List<MatchInfo> getMatchInfo() { + if (!mResultProto.hasSnippet()) { + return null; + } + AppSearch.Document document = getDocument(); + List<MatchInfo> matchList = new ArrayList<>(); + for (Iterator entryProtoIterator = mResultProto.getSnippet() + .getEntriesList().iterator(); entryProtoIterator.hasNext(); ) { + SnippetProto.EntryProto entry = (SnippetProto.EntryProto) entryProtoIterator.next(); + for (Iterator snippetMatchProtoIterator = entry.getSnippetMatchesList().iterator(); + snippetMatchProtoIterator.hasNext(); ) { + matchList.add(new MatchInfo(entry.getPropertyName(), + (SnippetMatchProto) snippetMatchProtoIterator.next(), document)); + } + } + return matchList; + } } @Override public String toString() { return mSearchResultProto.toString(); } - - /** - * Returns a {@link Result} iterator. Returns Empty Iterator if there are no matching results. - * @hide - */ - @NonNull - public Iterator<Result> getResults() { - List<Result> results = new ArrayList<>(); - // TODO(sidchhabra): Pass results using a RemoteStream. - for (SearchResultProto.ResultProto resultProto : mSearchResultProto.getResultsList()) { - results.add(new Result(resultProto)); - } - return results.iterator(); - } } diff --git a/apex/appsearch/framework/java/android/app/appsearch/SearchSpec.java b/apex/appsearch/framework/java/android/app/appsearch/SearchSpec.java index 5df7108fec09..c276ae1fe45e 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/SearchSpec.java +++ b/apex/appsearch/framework/java/android/app/appsearch/SearchSpec.java @@ -19,25 +19,32 @@ package android.app.appsearch; import android.annotation.IntDef; import android.annotation.NonNull; +import com.google.android.icing.proto.ResultSpecProto; +import com.google.android.icing.proto.ScoringSpecProto; import com.google.android.icing.proto.SearchSpecProto; import com.google.android.icing.proto.TermMatchType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; + /** * This class represents the specification logic for AppSearch. It can be used to set the type of * search, like prefix or exact only or apply filters to search for a specific schema type only etc. * @hide - * */ // TODO(sidchhabra) : AddResultSpec fields for Snippets etc. public final class SearchSpec { private final SearchSpecProto mSearchSpecProto; + private final ResultSpecProto mResultSpecProto; + private final ScoringSpecProto mScoringSpecProto; - private SearchSpec(SearchSpecProto searchSpecProto) { + private SearchSpec(@NonNull SearchSpecProto searchSpecProto, + @NonNull ResultSpecProto resultSpecProto, @NonNull ScoringSpecProto scoringSpecProto) { mSearchSpecProto = searchSpecProto; + mResultSpecProto = resultSpecProto; + mScoringSpecProto = scoringSpecProto; } /** Creates a new {@link SearchSpec.Builder}. */ @@ -48,10 +55,22 @@ public final class SearchSpec { /** @hide */ @NonNull - SearchSpecProto getProto() { + SearchSpecProto getSearchSpecProto() { return mSearchSpecProto; } + /** @hide */ + @NonNull + ResultSpecProto getResultSpecProto() { + return mResultSpecProto; + } + + /** @hide */ + @NonNull + ScoringSpecProto getScoringSpecProto() { + return mScoringSpecProto; + } + /** Term Match Type for the query. */ // NOTE: The integer values of these constants must match the proto enum constants in // {@link com.google.android.icing.proto.SearchSpecProto.termMatchType} @@ -62,51 +81,164 @@ public final class SearchSpec { @Retention(RetentionPolicy.SOURCE) public @interface TermMatchTypeCode {} + /** + * Query terms will only match exact tokens in the index. + * <p>Ex. A query term "foo" will only match indexed token "foo", and not "foot" or "football". + */ public static final int TERM_MATCH_TYPE_EXACT_ONLY = 1; + /** + * Query terms will match indexed tokens when the query term is a prefix of the token. + * <p>Ex. A query term "foo" will match indexed tokens like "foo", "foot", and "football". + */ public static final int TERM_MATCH_TYPE_PREFIX = 2; + /** Ranking Strategy for query result.*/ + // NOTE: The integer values of these constants must match the proto enum constants in + // {@link ScoringSpecProto.RankingStrategy.Code } + @IntDef(prefix = {"RANKING_STRATEGY_"}, value = { + RANKING_STRATEGY_NONE, + RANKING_STRATEGY_DOCUMENT_SCORE, + RANKING_STRATEGY_CREATION_TIMESTAMP + }) + @Retention(RetentionPolicy.SOURCE) + public @interface RankingStrategyCode {} + + /** No Ranking, results are returned in arbitrary order.*/ + public static final int RANKING_STRATEGY_NONE = 0; + /** Ranked by app-provided document scores. */ + public static final int RANKING_STRATEGY_DOCUMENT_SCORE = 1; + /** Ranked by document creation timestamps. */ + public static final int RANKING_STRATEGY_CREATION_TIMESTAMP = 2; + + /** Order for query result.*/ + // NOTE: The integer values of these constants must match the proto enum constants in + // {@link ScoringSpecProto.Order.Code } + @IntDef(prefix = {"ORDER_"}, value = { + ORDER_DESCENDING, + ORDER_ASCENDING + }) + @Retention(RetentionPolicy.SOURCE) + public @interface OrderCode {} + + /** Search results will be returned in a descending order. */ + public static final int ORDER_DESCENDING = 0; + /** Search results will be returned in an ascending order. */ + public static final int ORDER_ASCENDING = 1; + /** Builder for {@link SearchSpec objects}. */ public static final class Builder { - private final SearchSpecProto.Builder mBuilder = SearchSpecProto.newBuilder(); + private final SearchSpecProto.Builder mSearchSpecBuilder = SearchSpecProto.newBuilder(); + private final ResultSpecProto.Builder mResultSpecBuilder = ResultSpecProto.newBuilder(); + private final ScoringSpecProto.Builder mScoringSpecBuilder = ScoringSpecProto.newBuilder(); + private final ResultSpecProto.SnippetSpecProto.Builder mSnippetSpecBuilder = + ResultSpecProto.SnippetSpecProto.newBuilder(); - private Builder(){} + private Builder() { + } /** * Indicates how the query terms should match {@link TermMatchTypeCode} in the index. - * - * TermMatchType.Code=EXACT_ONLY - * Query terms will only match exact tokens in the index. - * Ex. A query term "foo" will only match indexed token "foo", and not "foot" - * or "football" - * - * TermMatchType.Code=PREFIX - * Query terms will match indexed tokens when the query term is a prefix of - * the token. - * Ex. A query term "foo" will match indexed tokens like "foo", "foot", and - * "football". */ @NonNull public Builder setTermMatchType(@TermMatchTypeCode int termMatchTypeCode) { TermMatchType.Code termMatchTypeCodeProto = TermMatchType.Code.forNumber(termMatchTypeCode); if (termMatchTypeCodeProto == null) { - throw new IllegalArgumentException("Invalid term match type: " + termMatchTypeCode); + throw new IllegalArgumentException("Invalid term match type: " + + termMatchTypeCode); } - mBuilder.setTermMatchType(termMatchTypeCodeProto); + mSearchSpecBuilder.setTermMatchType(termMatchTypeCodeProto); return this; } /** - * Adds a Schema type filter to {@link SearchSpec} Entry. - * Only search for documents that have the specified schema types. - * If unset, the query will search over all schema types. + * Adds a Schema type filter to {@link SearchSpec} Entry. Only search for documents that + * have the specified schema types. + * <p>If unset, the query will search over all schema types. */ @NonNull public Builder setSchemaTypes(@NonNull String... schemaTypes) { for (String schemaType : schemaTypes) { - mBuilder.addSchemaTypeFilters(schemaType); + mSearchSpecBuilder.addSchemaTypeFilters(schemaType); + } + return this; + } + + /** Sets the maximum number of results to retrieve from the query */ + @NonNull + public SearchSpec.Builder setNumToRetrieve(int numToRetrieve) { + mResultSpecBuilder.setNumToRetrieve(numToRetrieve); + return this; + } + + /** Sets ranking strategy for AppSearch results.*/ + @NonNull + public Builder setRankingStrategy(@RankingStrategyCode int rankingStrategy) { + ScoringSpecProto.RankingStrategy.Code rankingStrategyCodeProto = + ScoringSpecProto.RankingStrategy.Code.forNumber(rankingStrategy); + if (rankingStrategyCodeProto == null) { + throw new IllegalArgumentException("Invalid result ranking strategy: " + + rankingStrategyCodeProto); } + mScoringSpecBuilder.setRankBy(rankingStrategyCodeProto); + return this; + } + + /** + * Indicates the order of returned search results, the default is DESC, meaning that results + * with higher scores come first. + * <p>This order field will be ignored if RankingStrategy = {@code RANKING_STRATEGY_NONE}. + */ + @NonNull + public Builder setOrder(@OrderCode int order) { + ScoringSpecProto.Order.Code orderCodeProto = + ScoringSpecProto.Order.Code.forNumber(order); + if (orderCodeProto == null) { + throw new IllegalArgumentException("Invalid result ranking order: " + + orderCodeProto); + } + mScoringSpecBuilder.setOrderBy(orderCodeProto); + return this; + } + + /** + * Only the first {@code numToSnippet} documents based on the ranking strategy + * will have snippet information provided. + * <p>If set to 0 (default), snippeting is disabled and + * {@link SearchResults.Result#getMatchInfo} will return {@code null} for that result. + */ + @NonNull + public SearchSpec.Builder setNumToSnippet(int numToSnippet) { + mSnippetSpecBuilder.setNumToSnippet(numToSnippet); + return this; + } + + /** + * Only the first {@code numMatchesPerProperty} matches for a every property of + * {@link AppSearchDocument} will contain snippet information. + * <p>If set to 0, snippeting is disabled and {@link SearchResults.Result#getMatchInfo} + * will return {@code null} for that result. + */ + @NonNull + public SearchSpec.Builder setNumMatchesPerProperty(int numMatchesPerProperty) { + mSnippetSpecBuilder.setNumMatchesPerProperty(numMatchesPerProperty); + return this; + } + + /** + * Sets {@code maxSnippetSize}, the maximum snippet size. Snippet windows start at + * {@code maxSnippetSize/2} bytes before the middle of the matching token and end at + * {@code maxSnippetSize/2} bytes after the middle of the matching token. It respects + * token boundaries, therefore the returned window may be smaller than requested. + * <p> Setting {@code maxSnippetSize} to 0 will disable windowing and an empty string will + * be returned. If matches enabled is also set to false, then snippeting is disabled. + * <p>Ex. {@code maxSnippetSize} = 16. "foo bar baz bat rat" with a query of "baz" will + * return a window of "bar baz bat" which is only 11 bytes long. + */ + @NonNull + public SearchSpec.Builder setMaxSnippetSize(int maxSnippetSize) { + mSnippetSpecBuilder.setMaxWindowBytes(maxSnippetSize); return this; } @@ -117,11 +249,12 @@ public final class SearchSpec { */ @NonNull public SearchSpec build() { - if (mBuilder.getTermMatchType() == TermMatchType.Code.UNKNOWN) { + if (mSearchSpecBuilder.getTermMatchType() == TermMatchType.Code.UNKNOWN) { throw new IllegalSearchSpecException("Missing termMatchType field."); } - return new SearchSpec(mBuilder.build()); + mResultSpecBuilder.setSnippetSpec(mSnippetSpecBuilder); + return new SearchSpec(mSearchSpecBuilder.build(), mResultSpecBuilder.build(), + mScoringSpecBuilder.build()); } } - } 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 d2d9cf9fdf17..6293ee7059e5 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java @@ -106,10 +106,11 @@ public class AppSearchManagerService extends SystemService { // TODO(sidchhabra):Init FakeIcing properly. // TODO(sidchhabra): Do this in a threadpool. @Override - public void query(@NonNull String queryExpression, @NonNull byte[] searchSpec, - AndroidFuture callback) { - Preconditions.checkNotNull(queryExpression); + public void query(@NonNull byte[] searchSpec, @NonNull byte[] resultSpec, + @NonNull byte[] scoringSpec, AndroidFuture callback) { Preconditions.checkNotNull(searchSpec); + Preconditions.checkNotNull(resultSpec); + Preconditions.checkNotNull(scoringSpec); SearchSpecProto searchSpecProto = null; try { searchSpecProto = SearchSpecProto.parseFrom(searchSpec); @@ -117,7 +118,7 @@ public class AppSearchManagerService extends SystemService { throw new RuntimeException(e); } SearchResultProto searchResults = - mFakeIcing.query(queryExpression); + mFakeIcing.query(searchSpecProto.getQuery()); callback.complete(searchResults.toByteArray()); } } diff --git a/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java b/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java index 02a79a11032f..d07ef4bc3bf5 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java @@ -25,6 +25,7 @@ import android.util.SparseArray; import com.google.android.icing.proto.DocumentProto; import com.google.android.icing.proto.PropertyProto; import com.google.android.icing.proto.SearchResultProto; +import com.google.android.icing.proto.StatusProto; import java.util.Locale; import java.util.Map; @@ -97,10 +98,12 @@ public class FakeIcing { public SearchResultProto query(@NonNull String term) { String normTerm = normalizeString(term); Set<Integer> docIds = mIndex.get(normTerm); + SearchResultProto.Builder results = SearchResultProto.newBuilder() + .setStatus(StatusProto.newBuilder().setCode(StatusProto.Code.OK)); if (docIds == null || docIds.isEmpty()) { - return SearchResultProto.getDefaultInstance(); + return results.build(); } - SearchResultProto.Builder results = SearchResultProto.newBuilder(); + for (int docId : docIds) { DocumentProto document = mDocStore.get(docId); if (document != null) { diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java index ef1351e6d597..b96161aba758 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java @@ -48,6 +48,13 @@ public class JobParameters implements Parcelable { public static final int REASON_DEVICE_IDLE = JobProtoEnums.STOP_REASON_DEVICE_IDLE; // 4. /** @hide */ public static final int REASON_DEVICE_THERMAL = JobProtoEnums.STOP_REASON_DEVICE_THERMAL; // 5. + /** + * The job is in the {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} + * bucket. + * + * @hide + */ + public static final int REASON_RESTRAINED = JobProtoEnums.STOP_REASON_RESTRAINED; // 6. /** * All the stop reason codes. This should be regarded as an immutable array at runtime. @@ -65,6 +72,7 @@ public class JobParameters implements Parcelable { REASON_TIMEOUT, REASON_DEVICE_IDLE, REASON_DEVICE_THERMAL, + REASON_RESTRAINED, }; /** @@ -80,6 +88,7 @@ public class JobParameters implements Parcelable { case REASON_TIMEOUT: return "timeout"; case REASON_DEVICE_IDLE: return "device_idle"; case REASON_DEVICE_THERMAL: return "thermal"; + case REASON_RESTRAINED: return "restrained"; default: return "unknown:" + reason; } } diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java index 6109b713de24..d2d942a4a7e5 100644 --- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java +++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java @@ -103,6 +103,8 @@ public interface AppStandbyInternal { /** * Changes an app's standby bucket to the provided value. The caller can only set the standby * bucket for a different app than itself. + * If attempting to automatically place an app in the RESTRICTED bucket, use + * {@link #restrictApp(String, int, int)} instead. */ void setAppStandbyBucket(@NonNull String packageName, int bucket, int userId, int callingUid, int callingPid); @@ -113,6 +115,17 @@ public interface AppStandbyInternal { void setAppStandbyBuckets(@NonNull List<AppStandbyInfo> appBuckets, int userId, int callingUid, int callingPid); + /** + * Put the specified app in the + * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} + * bucket. If it has been used by the user recently, the restriction will delayed until an + * appropriate time. + * + * @param restrictReason The restrictReason for restricting the app. Should be one of the + * UsageStatsManager.REASON_SUB_RESTRICT_* reasons. + */ + void restrictApp(@NonNull String packageName, int userId, int restrictReason); + void addActiveDeviceAdmin(String adminPkg, int userId); void setActiveAdminApps(Set<String> adminPkgs, int userId); 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 102e8485aac5..ed5626a1acbc 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -101,6 +101,7 @@ import com.android.server.job.controllers.DeviceIdleJobsController; import com.android.server.job.controllers.IdleController; import com.android.server.job.controllers.JobStatus; import com.android.server.job.controllers.QuotaController; +import com.android.server.job.controllers.RestrictingController; import com.android.server.job.controllers.StateController; import com.android.server.job.controllers.StorageController; import com.android.server.job.controllers.TimeController; @@ -241,6 +242,11 @@ public class JobSchedulerService extends com.android.server.SystemService /** List of controllers that will notify this service of updates to jobs. */ final List<StateController> mControllers; + /** + * List of controllers that will apply to all jobs in the RESTRICTED bucket. This is a subset of + * {@link #mControllers}. + */ + private final List<RestrictingController> mRestrictiveControllers; /** Need direct access to this for testing. */ private final BatteryController mBatteryController; /** Need direct access to this for testing. */ @@ -277,6 +283,7 @@ public class JobSchedulerService extends com.android.server.SystemService DeviceIdleInternal mLocalDeviceIdleController; AppStateTracker mAppStateTracker; final UsageStatsManagerInternal mUsageStats; + private final AppStandbyInternal mAppStandbyInternal; /** * Set to true once we are allowed to run third party apps. @@ -312,6 +319,9 @@ public class JobSchedulerService extends com.android.server.SystemService public static final int FREQUENT_INDEX = 2; public static final int RARE_INDEX = 3; public static final int NEVER_INDEX = 4; + // Putting RESTRICTED_INDEX after NEVER_INDEX to make it easier for proto dumping + // (ScheduledJobStateChanged and JobStatusDumpProto). + public static final int RESTRICTED_INDEX = 5; // -- Pre-allocated temporaries only for use in assignJobsToContextsLocked -- @@ -1062,7 +1072,8 @@ public class JobSchedulerService extends com.android.server.SystemService 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 + mAppStandbyInternal.restrictApp( + pkg, userId, UsageStatsManager.REASON_SUB_RESTRICT_BUGGY); if (mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION && mPlatformCompat.isChangeEnabledByPackageName( CRASH_ON_EXCEEDED_LIMIT, pkg, userId)) { @@ -1365,6 +1376,40 @@ public class JobSchedulerService extends com.android.server.SystemService } } + @Override + public void onRestrictedBucketChanged(List<JobStatus> jobs) { + final int len = jobs.size(); + if (len == 0) { + Slog.wtf(TAG, "onRestrictedBucketChanged called with no jobs"); + return; + } + synchronized (mLock) { + for (int i = 0; i < len; ++i) { + JobStatus js = jobs.get(i); + for (int j = mRestrictiveControllers.size() - 1; j >= 0; --j) { + // Effective standby bucket can change after this in some situations so use + // the real bucket so that the job is tracked by the controllers. + if (js.getStandbyBucket() == RESTRICTED_INDEX) { + js.addDynamicConstraint(JobStatus.CONSTRAINT_BATTERY_NOT_LOW); + js.addDynamicConstraint(JobStatus.CONSTRAINT_CHARGING); + js.addDynamicConstraint(JobStatus.CONSTRAINT_CONNECTIVITY); + js.addDynamicConstraint(JobStatus.CONSTRAINT_IDLE); + + mRestrictiveControllers.get(j).startTrackingRestrictedJobLocked(js); + } else { + js.removeDynamicConstraint(JobStatus.CONSTRAINT_BATTERY_NOT_LOW); + js.removeDynamicConstraint(JobStatus.CONSTRAINT_CHARGING); + js.removeDynamicConstraint(JobStatus.CONSTRAINT_CONNECTIVITY); + js.removeDynamicConstraint(JobStatus.CONSTRAINT_IDLE); + + mRestrictiveControllers.get(j).stopTrackingRestrictedJobLocked(js); + } + } + } + } + mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); + } + void reportActiveLocked() { // active is true if pending queue contains jobs OR some job is running. boolean active = mPendingJobs.size() > 0; @@ -1430,8 +1475,8 @@ public class JobSchedulerService extends com.android.server.SystemService mConstants.API_QUOTA_SCHEDULE_COUNT, mConstants.API_QUOTA_SCHEDULE_WINDOW_MS); - AppStandbyInternal appStandby = LocalServices.getService(AppStandbyInternal.class); - appStandby.addListener(mStandbyTracker); + mAppStandbyInternal = LocalServices.getService(AppStandbyInternal.class); + mAppStandbyInternal.addListener(mStandbyTracker); // The job store needs to call back publishLocalService(JobSchedulerInternal.class, new LocalService()); @@ -1441,9 +1486,11 @@ public class JobSchedulerService extends com.android.server.SystemService // Create the controllers. mControllers = new ArrayList<StateController>(); - mControllers.add(new ConnectivityController(this)); + final ConnectivityController connectivityController = new ConnectivityController(this); + mControllers.add(connectivityController); mControllers.add(new TimeController(this)); - mControllers.add(new IdleController(this)); + final IdleController idleController = new IdleController(this); + mControllers.add(idleController); mBatteryController = new BatteryController(this); mControllers.add(mBatteryController); mStorageController = new StorageController(this); @@ -1455,6 +1502,11 @@ public class JobSchedulerService extends com.android.server.SystemService mQuotaController = new QuotaController(this); mControllers.add(mQuotaController); + mRestrictiveControllers = new ArrayList<>(); + mRestrictiveControllers.add(mBatteryController); + mRestrictiveControllers.add(connectivityController); + mRestrictiveControllers.add(idleController); + // Create restrictions mJobRestrictions = new ArrayList<>(); mJobRestrictions.add(new ThermalStatusRestriction(this)); @@ -2125,11 +2177,13 @@ public class JobSchedulerService extends com.android.server.SystemService } } catch (RemoteException e) { } - if (mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT > 1 + // Restricted jobs must always be batched + if (job.getEffectiveStandbyBucket() == RESTRICTED_INDEX + || (mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT > 1 && job.getEffectiveStandbyBucket() != ACTIVE_INDEX && (job.getFirstForceBatchedTimeElapsed() == 0 || sElapsedRealtimeClock.millis() - job.getFirstForceBatchedTimeElapsed() - < mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS)) { + < mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS))) { // Force batching non-ACTIVE jobs. Don't include them in the other counts. forceBatchedCount++; if (job.getFirstForceBatchedTimeElapsed() == 0) { @@ -2536,11 +2590,19 @@ public class JobSchedulerService extends com.android.server.SystemService public static int standbyBucketToBucketIndex(int bucket) { // Normalize AppStandby constants to indices into our bookkeeping - if (bucket == UsageStatsManager.STANDBY_BUCKET_NEVER) return NEVER_INDEX; - else if (bucket > UsageStatsManager.STANDBY_BUCKET_FREQUENT) return RARE_INDEX; - else if (bucket > UsageStatsManager.STANDBY_BUCKET_WORKING_SET) return FREQUENT_INDEX; - else if (bucket > UsageStatsManager.STANDBY_BUCKET_ACTIVE) return WORKING_INDEX; - else return ACTIVE_INDEX; + if (bucket == UsageStatsManager.STANDBY_BUCKET_NEVER) { + return NEVER_INDEX; + } else if (bucket > UsageStatsManager.STANDBY_BUCKET_RARE) { + return RESTRICTED_INDEX; + } else if (bucket > UsageStatsManager.STANDBY_BUCKET_FREQUENT) { + return RARE_INDEX; + } else if (bucket > UsageStatsManager.STANDBY_BUCKET_WORKING_SET) { + return FREQUENT_INDEX; + } else if (bucket > UsageStatsManager.STANDBY_BUCKET_ACTIVE) { + return WORKING_INDEX; + } else { + return ACTIVE_INDEX; + } } // Static to support external callers diff --git a/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java b/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java index 87bfc27a715f..cb3c43714111 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java +++ b/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java @@ -16,8 +16,12 @@ package com.android.server.job; +import android.annotation.NonNull; + import com.android.server.job.controllers.JobStatus; +import java.util.List; + /** * Interface through which a {@link com.android.server.job.controllers.StateController} informs * the {@link com.android.server.job.JobSchedulerService} that there are some tasks potentially @@ -39,4 +43,10 @@ public interface StateChangedListener { public void onRunJobNow(JobStatus jobStatus); public void onDeviceIdleStateChanged(boolean deviceIdle); + + /** + * Called when these jobs are added or removed from the + * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} bucket. + */ + void onRestrictedBucketChanged(@NonNull List<JobStatus> jobs); } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java index 46658ad33b85..461ef21af7ee 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java @@ -43,7 +43,7 @@ import java.util.function.Predicate; * be charging when it's been plugged in for more than two minutes, and the system has broadcast * ACTION_BATTERY_OK. */ -public final class BatteryController extends StateController { +public final class BatteryController extends RestrictingController { private static final String TAG = "JobScheduler.Battery"; private static final boolean DEBUG = JobSchedulerService.DEBUG || Log.isLoggable(TAG, Log.DEBUG); @@ -73,12 +73,24 @@ public final class BatteryController extends StateController { } @Override + public void startTrackingRestrictedJobLocked(JobStatus jobStatus) { + maybeStartTrackingJobLocked(jobStatus, null); + } + + @Override public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate) { if (taskStatus.clearTrackingController(JobStatus.TRACKING_BATTERY)) { mTrackedTasks.remove(taskStatus); } } + @Override + public void stopTrackingRestrictedJobLocked(JobStatus jobStatus) { + if (!jobStatus.hasPowerConstraint()) { + maybeStopTrackingJobLocked(jobStatus, null, false); + } + } + private void maybeReportNewChargingStateLocked() { final boolean stablePower = mChargeTracker.isOnStablePower(); final boolean batteryNotLow = mChargeTracker.isBatteryNotLow(); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java index 8eeea1ba17e2..a0e83daf877d 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java @@ -20,6 +20,8 @@ import static android.net.NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; +import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX; + import android.app.job.JobInfo; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; @@ -63,7 +65,7 @@ import java.util.function.Predicate; * * Test: atest com.android.server.job.controllers.ConnectivityControllerTest */ -public final class ConnectivityController extends StateController implements +public final class ConnectivityController extends RestrictingController implements ConnectivityManager.OnNetworkActiveListener { private static final String TAG = "JobScheduler.Connectivity"; private static final boolean DEBUG = JobSchedulerService.DEBUG @@ -138,8 +140,22 @@ public final class ConnectivityController extends StateController implements } } + @Override + public void startTrackingRestrictedJobLocked(JobStatus jobStatus) { + // Don't need to start tracking the job. If the job needed network, it would already be + // tracked. + updateConstraintsSatisfied(jobStatus); + } + + @Override + public void stopTrackingRestrictedJobLocked(JobStatus jobStatus) { + // Shouldn't stop tracking the job here. If the job was tracked, it still needs network, + // even after being unrestricted. + updateConstraintsSatisfied(jobStatus); + } + /** - * Returns true if the job's requested network is available. This DOES NOT necesarilly mean + * Returns true if the job's requested network is available. This DOES NOT necessarily mean * that the UID has been granted access to the network. */ public boolean isNetworkAvailable(JobStatus job) { @@ -353,14 +369,24 @@ public final class ConnectivityController extends StateController implements private static boolean isStrictSatisfied(JobStatus jobStatus, Network network, NetworkCapabilities capabilities, Constants constants) { - return jobStatus.getJob().getRequiredNetwork().networkCapabilities - .satisfiedByNetworkCapabilities(capabilities); + final NetworkCapabilities required; + // A restricted job that's out of quota MUST use an unmetered network. + if (jobStatus.getEffectiveStandbyBucket() == RESTRICTED_INDEX + && !jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)) { + required = new NetworkCapabilities( + jobStatus.getJob().getRequiredNetwork().networkCapabilities) + .addCapability(NET_CAPABILITY_NOT_METERED); + } else { + required = jobStatus.getJob().getRequiredNetwork().networkCapabilities; + } + + return required.satisfiedByNetworkCapabilities(capabilities); } private static boolean isRelaxedSatisfied(JobStatus jobStatus, Network network, NetworkCapabilities capabilities, Constants constants) { - // Only consider doing this for prefetching jobs - if (!jobStatus.getJob().isPrefetch()) { + // Only consider doing this for unrestricted prefetching jobs + if (!jobStatus.getJob().isPrefetch() || jobStatus.getStandbyBucket() == RESTRICTED_INDEX) { return false; } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java index d355715920b6..c0b3204192d6 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java @@ -32,7 +32,15 @@ import com.android.server.job.controllers.idle.IdlenessTracker; import java.util.function.Predicate; -public final class IdleController extends StateController implements IdlenessListener { +/** + * Simple controller that tracks whether the device is idle or not. Idleness depends on the device + * type and is not related to device-idle (Doze mode) despite the similar naming. + * + * @see CarIdlenessTracker + * @see DeviceIdlenessTracker + * @see IdlenessTracker + */ +public final class IdleController extends RestrictingController implements IdlenessListener { private static final String TAG = "JobScheduler.IdleController"; // Policy: we decide that we're "idle" if the device has been unused / // screen off or dreaming or wireless charging dock idle for at least this long @@ -57,6 +65,11 @@ public final class IdleController extends StateController implements IdlenessLis } @Override + public void startTrackingRestrictedJobLocked(JobStatus jobStatus) { + maybeStartTrackingJobLocked(jobStatus, null); + } + + @Override public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate) { if (taskStatus.clearTrackingController(JobStatus.TRACKING_IDLE)) { @@ -64,6 +77,13 @@ public final class IdleController extends StateController implements IdlenessLis } } + @Override + public void stopTrackingRestrictedJobLocked(JobStatus jobStatus) { + if (!jobStatus.hasIdleConstraint()) { + maybeStopTrackingJobLocked(jobStatus, null, false); + } + } + /** * State-change notifications from the idleness tracker */ diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index a8d8bd9b2621..dbdce70dda03 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -69,13 +69,14 @@ public final class JobStatus { public static final long NO_LATEST_RUNTIME = Long.MAX_VALUE; public static final long NO_EARLIEST_RUNTIME = 0L; - static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING; // 1 < 0 - static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE; // 1 << 2 - static final int CONSTRAINT_BATTERY_NOT_LOW = JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; // 1 << 1 + public static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING; // 1 < 0 + public static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE; // 1 << 2 + public static final int CONSTRAINT_BATTERY_NOT_LOW = + JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; // 1 << 1 static final int CONSTRAINT_STORAGE_NOT_LOW = JobInfo.CONSTRAINT_FLAG_STORAGE_NOT_LOW; // 1 << 3 static final int CONSTRAINT_TIMING_DELAY = 1<<31; static final int CONSTRAINT_DEADLINE = 1<<30; - static final int CONSTRAINT_CONNECTIVITY = 1<<28; + public static final int CONSTRAINT_CONNECTIVITY = 1 << 28; static final int CONSTRAINT_CONTENT_TRIGGER = 1<<26; static final int CONSTRAINT_DEVICE_NOT_DOZING = 1 << 25; // Implicit constraint static final int CONSTRAINT_WITHIN_QUOTA = 1 << 24; // Implicit constraint @@ -117,7 +118,7 @@ public final class JobStatus { /** The minimum possible update delay is 1/2 second. */ public static final long MIN_TRIGGER_UPDATE_DELAY = 500; - /** If not specified, trigger maxumum delay is 2 minutes. */ + /** If not specified, trigger maximum delay is 2 minutes. */ public static final long DEFAULT_TRIGGER_MAX_DELAY = 2*60*1000; /** The minimum possible update delay is 1 second. */ @@ -188,6 +189,11 @@ public final class JobStatus { private final int mRequiredConstraintsOfInterest; int satisfiedConstraints = 0; private int mSatisfiedConstraintsOfInterest = 0; + /** + * Set of constraints that must be satisfied for the job if/because it's in the RESTRICTED + * bucket. + */ + private int mDynamicConstraints = 0; // Set to true if doze constraint was satisfied due to app being whitelisted. public boolean dozeWhitelisted; @@ -328,6 +334,9 @@ public final class JobStatus { /** The job is within its quota based on its standby bucket. */ private boolean mReadyWithinQuota; + /** The job's dynamic requirements have been satisfied. */ + private boolean mReadyDynamicSatisfied; + /** Provide a handle to the service that this job will be run on. */ public int getServiceToken() { return callingUid; @@ -410,6 +419,7 @@ public final class JobStatus { this.requiredConstraints = requiredConstraints; mRequiredConstraintsOfInterest = requiredConstraints & CONSTRAINTS_OF_INTEREST; mReadyNotDozing = (job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0; + mReadyDynamicSatisfied = true; mLastSuccessfulRunTime = lastSuccessfulRunTime; mLastFailedRunTime = lastFailedRunTime; @@ -830,41 +840,54 @@ public final class JobStatus { /** Does this job have any sort of networking constraint? */ public boolean hasConnectivityConstraint() { + // No need to check mDynamicConstraints since connectivity will only be in that list if + // it's already in the requiredConstraints list. return (requiredConstraints&CONSTRAINT_CONNECTIVITY) != 0; } public boolean hasChargingConstraint() { - return (requiredConstraints&CONSTRAINT_CHARGING) != 0; + return hasConstraint(CONSTRAINT_CHARGING); } public boolean hasBatteryNotLowConstraint() { - return (requiredConstraints&CONSTRAINT_BATTERY_NOT_LOW) != 0; + return hasConstraint(CONSTRAINT_BATTERY_NOT_LOW); } - public boolean hasPowerConstraint() { - return (requiredConstraints&(CONSTRAINT_CHARGING|CONSTRAINT_BATTERY_NOT_LOW)) != 0; + /** Returns true if the job requires charging OR battery not low. */ + boolean hasPowerConstraint() { + return hasConstraint(CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW); } public boolean hasStorageNotLowConstraint() { - return (requiredConstraints&CONSTRAINT_STORAGE_NOT_LOW) != 0; + return hasConstraint(CONSTRAINT_STORAGE_NOT_LOW); } public boolean hasTimingDelayConstraint() { - return (requiredConstraints&CONSTRAINT_TIMING_DELAY) != 0; + return hasConstraint(CONSTRAINT_TIMING_DELAY); } public boolean hasDeadlineConstraint() { - return (requiredConstraints&CONSTRAINT_DEADLINE) != 0; + return hasConstraint(CONSTRAINT_DEADLINE); } public boolean hasIdleConstraint() { - return (requiredConstraints&CONSTRAINT_IDLE) != 0; + return hasConstraint(CONSTRAINT_IDLE); } public boolean hasContentTriggerConstraint() { + // No need to check mDynamicConstraints since content trigger will only be in that list if + // it's already in the requiredConstraints list. return (requiredConstraints&CONSTRAINT_CONTENT_TRIGGER) != 0; } + /** + * Checks both {@link #requiredConstraints} and {@link #mDynamicConstraints} to see if this job + * requires the specified constraint. + */ + private boolean hasConstraint(int constraint) { + return (requiredConstraints & constraint) != 0 || (mDynamicConstraints & constraint) != 0; + } + public long getTriggerContentUpdateDelay() { long time = job.getTriggerContentUpdateDelay(); if (time < 0) { @@ -1033,6 +1056,8 @@ public final class JobStatus { } satisfiedConstraints = (satisfiedConstraints&~constraint) | (state ? constraint : 0); mSatisfiedConstraintsOfInterest = satisfiedConstraints & CONSTRAINTS_OF_INTEREST; + mReadyDynamicSatisfied = + mDynamicConstraints == (satisfiedConstraints & mDynamicConstraints); if (STATS_LOG_ENABLED && (STATSD_CONSTRAINTS_TO_LOG & constraint) != 0) { StatsLog.write_non_chained(StatsLog.SCHEDULED_JOB_CONSTRAINT_CHANGED, sourceUid, null, getBatteryName(), getProtoConstraint(constraint), @@ -1058,6 +1083,43 @@ public final class JobStatus { trackingControllers |= which; } + /** + * Indicates that this job cannot run without the specified constraint. This is evaluated + * separately from the job's explicitly requested constraints and MUST be satisfied before + * the job can run if the app doesn't have quota. + * + */ + public void addDynamicConstraint(int constraint) { + if (constraint == CONSTRAINT_WITHIN_QUOTA) { + Slog.wtf(TAG, "Tried to set quota as a dynamic constraint"); + return; + } + + // Connectivity and content trigger are special since they're only valid to add if the + // job has requested network or specific content URIs. Adding these constraints to jobs + // that don't need them doesn't make sense. + if ((constraint == CONSTRAINT_CONNECTIVITY && !hasConnectivityConstraint()) + || (constraint == CONSTRAINT_CONTENT_TRIGGER && !hasContentTriggerConstraint())) { + return; + } + + mDynamicConstraints |= constraint; + mReadyDynamicSatisfied = + mDynamicConstraints == (satisfiedConstraints & mDynamicConstraints); + } + + /** + * Removes a dynamic constraint from a job, meaning that the requirement is not required for + * the job to run (if the job itself hasn't requested the constraint. This is separate from + * the job's explicitly requested constraints and does not remove those requested constraints. + * + */ + public void removeDynamicConstraint(int constraint) { + mDynamicConstraints &= ~constraint; + mReadyDynamicSatisfied = + mDynamicConstraints == (satisfiedConstraints & mDynamicConstraints); + } + public long getLastSuccessfulRunTime() { return mLastSuccessfulRunTime; } @@ -1099,6 +1161,8 @@ public final class JobStatus { break; default: satisfied |= constraint; + mReadyDynamicSatisfied = + mDynamicConstraints == (satisfied & mDynamicConstraints); break; } @@ -1117,24 +1181,29 @@ public final class JobStatus { case CONSTRAINT_WITHIN_QUOTA: mReadyWithinQuota = oldValue; break; + default: + mReadyDynamicSatisfied = + mDynamicConstraints == (satisfiedConstraints & mDynamicConstraints); + break; } return toReturn; } private boolean isReady(int satisfiedConstraints) { - // Quota constraints trumps all other constraints. - if (!mReadyWithinQuota) { + // Quota and dynamic constraints trump all other constraints. + if (!mReadyWithinQuota && !mReadyDynamicSatisfied) { return false; } - // Deadline constraint trumps other constraints besides quota (except for periodic jobs - // where deadline is an implementation detail. A periodic job should only run if its - // constraints are satisfied). + // Deadline constraint trumps other constraints besides quota and dynamic (except for + // periodic jobs where deadline is an implementation detail. A periodic job should only + // run if its constraints are satisfied). // DeviceNotDozing implicit constraint must be satisfied // NotRestrictedInBackground implicit constraint must be satisfied return mReadyNotDozing && mReadyNotRestrictedInBg && (mReadyDeadlineSatisfied || isConstraintsSatisfied(satisfiedConstraints)); } + /** All constraints besides implicit and deadline. */ static final int CONSTRAINTS_OF_INTEREST = CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW | CONSTRAINT_STORAGE_NOT_LOW | CONSTRAINT_TIMING_DELAY | CONSTRAINT_CONNECTIVITY | CONSTRAINT_IDLE | CONSTRAINT_CONTENT_TRIGGER; @@ -1441,6 +1510,8 @@ public final class JobStatus { case 2: return "FREQUENT"; case 3: return "RARE"; case 4: return "NEVER"; + case 5: + return "RESTRICTED"; default: return "Unknown: " + standbyBucket; } @@ -1560,6 +1631,10 @@ public final class JobStatus { pw.print(prefix); pw.print("Required constraints:"); dumpConstraints(pw, requiredConstraints); pw.println(); + pw.print(prefix); + pw.print("Dynamic constraints:"); + dumpConstraints(pw, mDynamicConstraints); + pw.println(); if (full) { pw.print(prefix); pw.print("Satisfied constraints:"); dumpConstraints(pw, satisfiedConstraints); @@ -1599,6 +1674,9 @@ public final class JobStatus { pw.print(prefix); pw.print(" readyDeadlineSatisfied: "); pw.println(mReadyDeadlineSatisfied); } + pw.print(prefix); + pw.print(" readyDynamicSatisfied: "); + pw.println(mReadyDynamicSatisfied); if (changedAuthorities != null) { pw.print(prefix); pw.println("Changed authorities:"); @@ -1760,6 +1838,7 @@ public final class JobStatus { } dumpConstraints(proto, JobStatusDumpProto.REQUIRED_CONSTRAINTS, requiredConstraints); + dumpConstraints(proto, JobStatusDumpProto.DYNAMIC_CONSTRAINTS, mDynamicConstraints); if (full) { dumpConstraints(proto, JobStatusDumpProto.SATISFIED_CONSTRAINTS, satisfiedConstraints); dumpConstraints(proto, JobStatusDumpProto.UNSATISFIED_CONSTRAINTS, @@ -1807,6 +1886,8 @@ public final class JobStatus { mReadyNotRestrictedInBg); // mReadyDeadlineSatisfied isn't an implicit constraint...and can be determined from other // field values. + proto.write(JobStatusDumpProto.ImplicitConstraints.IS_DYNAMIC_SATISFIED, + mReadyDynamicSatisfied); proto.end(icToken); if (changedAuthorities != null) { diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java index 2e735a4e45f5..8eefac8abdac 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java @@ -24,6 +24,7 @@ import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX; import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX; import static com.android.server.job.JobSchedulerService.NEVER_INDEX; import static com.android.server.job.JobSchedulerService.RARE_INDEX; +import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX; import static com.android.server.job.JobSchedulerService.WORKING_INDEX; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; @@ -424,7 +425,9 @@ public final class QuotaController extends StateController { QcConstants.DEFAULT_WINDOW_SIZE_ACTIVE_MS, QcConstants.DEFAULT_WINDOW_SIZE_WORKING_MS, QcConstants.DEFAULT_WINDOW_SIZE_FREQUENT_MS, - QcConstants.DEFAULT_WINDOW_SIZE_RARE_MS + QcConstants.DEFAULT_WINDOW_SIZE_RARE_MS, + 0, // NEVER + QcConstants.DEFAULT_WINDOW_SIZE_RESTRICTED_MS }; /** The maximum period any bucket can have. */ @@ -441,7 +444,9 @@ public final class QuotaController extends StateController { QcConstants.DEFAULT_MAX_JOB_COUNT_ACTIVE, QcConstants.DEFAULT_MAX_JOB_COUNT_WORKING, QcConstants.DEFAULT_MAX_JOB_COUNT_FREQUENT, - QcConstants.DEFAULT_MAX_JOB_COUNT_RARE + QcConstants.DEFAULT_MAX_JOB_COUNT_RARE, + 0, // NEVER + QcConstants.DEFAULT_MAX_JOB_COUNT_RESTRICTED }; /** @@ -455,7 +460,9 @@ public final class QuotaController extends StateController { QcConstants.DEFAULT_MAX_SESSION_COUNT_ACTIVE, QcConstants.DEFAULT_MAX_SESSION_COUNT_WORKING, QcConstants.DEFAULT_MAX_SESSION_COUNT_FREQUENT, - QcConstants.DEFAULT_MAX_SESSION_COUNT_RARE + QcConstants.DEFAULT_MAX_SESSION_COUNT_RARE, + 0, // NEVER + QcConstants.DEFAULT_MAX_SESSION_COUNT_RESTRICTED, }; /** @@ -648,7 +655,11 @@ public final class QuotaController extends StateController { // Quota constraint is not enforced while charging. if (mChargeTracker.isCharging()) { - return true; + // Restricted jobs require additional constraints when charging, so don't immediately + // mark quota as free when charging. + if (standbyBucket != RESTRICTED_INDEX) { + return true; + } } ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket); @@ -1105,14 +1116,37 @@ public final class QuotaController extends StateController { } } + private class TimerChargingUpdateFunctor implements Consumer<Timer> { + private long mNowElapsed; + private boolean mIsCharging; + + private void setStatus(long nowElapsed, boolean isCharging) { + mNowElapsed = nowElapsed; + mIsCharging = isCharging; + } + + @Override + public void accept(Timer timer) { + if (JobSchedulerService.standbyBucketForPackage(timer.mPkg.packageName, + timer.mPkg.userId, mNowElapsed) != RESTRICTED_INDEX) { + // Restricted jobs need additional constraints even when charging, so don't + // immediately say that quota is free. + timer.onStateChangedLocked(mNowElapsed, mIsCharging); + } + } + } + + private final TimerChargingUpdateFunctor + mTimerChargingUpdateFunctor = new TimerChargingUpdateFunctor(); + private void handleNewChargingStateLocked() { - final long nowElapsed = sElapsedRealtimeClock.millis(); - final boolean isCharging = mChargeTracker.isCharging(); + mTimerChargingUpdateFunctor.setStatus(sElapsedRealtimeClock.millis(), + mChargeTracker.isCharging()); if (DEBUG) { - Slog.d(TAG, "handleNewChargingStateLocked: " + isCharging); + Slog.d(TAG, "handleNewChargingStateLocked: " + mChargeTracker.isCharging()); } // Deal with Timers first. - mPkgTimers.forEach((t) -> t.onStateChangedLocked(nowElapsed, isCharging)); + mPkgTimers.forEach(mTimerChargingUpdateFunctor); // Now update jobs. maybeUpdateAllConstraintsLocked(); } @@ -1555,7 +1589,10 @@ public final class QuotaController extends StateController { } private boolean shouldTrackLocked() { - return !mChargeTracker.isCharging() && !mForegroundUids.get(mUid); + final int standbyBucket = JobSchedulerService.standbyBucketForPackage(mPkg.packageName, + mPkg.userId, sElapsedRealtimeClock.millis()); + return (standbyBucket == RESTRICTED_INDEX || !mChargeTracker.isCharging()) + && !mForegroundUids.get(mUid); } void onStateChangedLocked(long nowElapsed, boolean isQuotaFree) { @@ -1670,6 +1707,7 @@ public final class QuotaController extends StateController { Slog.i(TAG, "Moving pkg " + string(userId, packageName) + " to bucketIndex " + bucketIndex); } + List<JobStatus> restrictedChanges = new ArrayList<>(); synchronized (mLock) { ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, packageName); if (jobs == null || jobs.size() == 0) { @@ -1677,6 +1715,13 @@ public final class QuotaController extends StateController { } for (int i = jobs.size() - 1; i >= 0; i--) { JobStatus js = jobs.valueAt(i); + // Effective standby bucket can change after this in some situations so + // use the real bucket so that the job is tracked by the controllers. + if ((bucketIndex == RESTRICTED_INDEX + || js.getStandbyBucket() == RESTRICTED_INDEX) + && bucketIndex != js.getStandbyBucket()) { + restrictedChanges.add(js); + } js.setStandbyBucket(bucketIndex); } Timer timer = mPkgTimers.get(userId, packageName); @@ -1687,6 +1732,9 @@ public final class QuotaController extends StateController { mStateChangedListener.onControllerStateChanged(); } } + if (restrictedChanges.size() > 0) { + mStateChangedListener.onRestrictedBucketChanged(restrictedChanges); + } }); } } @@ -1863,11 +1911,13 @@ public final class QuotaController extends StateController { private static final String KEY_WINDOW_SIZE_WORKING_MS = "window_size_working_ms"; private static final String KEY_WINDOW_SIZE_FREQUENT_MS = "window_size_frequent_ms"; private static final String KEY_WINDOW_SIZE_RARE_MS = "window_size_rare_ms"; + private static final String KEY_WINDOW_SIZE_RESTRICTED_MS = "window_size_restricted_ms"; private static final String KEY_MAX_EXECUTION_TIME_MS = "max_execution_time_ms"; private static final String KEY_MAX_JOB_COUNT_ACTIVE = "max_job_count_active"; private static final String KEY_MAX_JOB_COUNT_WORKING = "max_job_count_working"; private static final String KEY_MAX_JOB_COUNT_FREQUENT = "max_job_count_frequent"; private static final String KEY_MAX_JOB_COUNT_RARE = "max_job_count_rare"; + private static final String KEY_MAX_JOB_COUNT_RESTRICTED = "max_job_count_restricted"; private static final String KEY_RATE_LIMITING_WINDOW_MS = "rate_limiting_window_ms"; private static final String KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = "max_job_count_per_rate_limiting_window"; @@ -1875,6 +1925,8 @@ public final class QuotaController extends StateController { private static final String KEY_MAX_SESSION_COUNT_WORKING = "max_session_count_working"; private static final String KEY_MAX_SESSION_COUNT_FREQUENT = "max_session_count_frequent"; private static final String KEY_MAX_SESSION_COUNT_RARE = "max_session_count_rare"; + private static final String KEY_MAX_SESSION_COUNT_RESTRICTED = + "max_session_count_restricted"; private static final String KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = "max_session_count_per_rate_limiting_window"; private static final String KEY_TIMING_SESSION_COALESCING_DURATION_MS = @@ -1892,6 +1944,8 @@ public final class QuotaController extends StateController { 8 * 60 * 60 * 1000L; // 8 hours private static final long DEFAULT_WINDOW_SIZE_RARE_MS = 24 * 60 * 60 * 1000L; // 24 hours + private static final long DEFAULT_WINDOW_SIZE_RESTRICTED_MS = + 24 * 60 * 60 * 1000L; // 24 hours private static final long DEFAULT_MAX_EXECUTION_TIME_MS = 4 * HOUR_IN_MILLIS; private static final long DEFAULT_RATE_LIMITING_WINDOW_MS = @@ -1905,6 +1959,7 @@ public final class QuotaController extends StateController { (int) (25.0 * DEFAULT_WINDOW_SIZE_FREQUENT_MS / HOUR_IN_MILLIS); private static final int DEFAULT_MAX_JOB_COUNT_RARE = // 48/window = 2/hr = 16/session (int) (2.0 * DEFAULT_WINDOW_SIZE_RARE_MS / HOUR_IN_MILLIS); + private static final int DEFAULT_MAX_JOB_COUNT_RESTRICTED = 10; private static final int DEFAULT_MAX_SESSION_COUNT_ACTIVE = 75; // 450/hr private static final int DEFAULT_MAX_SESSION_COUNT_WORKING = @@ -1913,6 +1968,7 @@ public final class QuotaController extends StateController { 8; // 1/hr private static final int DEFAULT_MAX_SESSION_COUNT_RARE = 3; // .125/hr + private static final int DEFAULT_MAX_SESSION_COUNT_RESTRICTED = 1; // 1/day private static final int DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 20; private static final long DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS = 5000; // 5 seconds @@ -1954,6 +2010,13 @@ public final class QuotaController extends StateController { public long WINDOW_SIZE_RARE_MS = DEFAULT_WINDOW_SIZE_RARE_MS; /** + * The quota window size of the particular standby bucket. Apps in this standby bucket are + * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past + * WINDOW_SIZE_MS. + */ + public long WINDOW_SIZE_RESTRICTED_MS = DEFAULT_WINDOW_SIZE_RESTRICTED_MS; + + /** * The maximum amount of time an app can have its jobs running within a 24 hour window. */ public long MAX_EXECUTION_TIME_MS = DEFAULT_MAX_EXECUTION_TIME_MS; @@ -1982,6 +2045,12 @@ public final class QuotaController extends StateController { */ public int MAX_JOB_COUNT_RARE = DEFAULT_MAX_JOB_COUNT_RARE; + /** + * The maximum number of jobs an app can run within this particular standby bucket's + * window size. + */ + public int MAX_JOB_COUNT_RESTRICTED = DEFAULT_MAX_JOB_COUNT_RESTRICTED; + /** The period of time used to rate limit recently run jobs. */ public long RATE_LIMITING_WINDOW_MS = DEFAULT_RATE_LIMITING_WINDOW_MS; @@ -2016,6 +2085,12 @@ public final class QuotaController extends StateController { public int MAX_SESSION_COUNT_RARE = DEFAULT_MAX_SESSION_COUNT_RARE; /** + * The maximum number of {@link TimingSession}s an app can run within this particular + * standby bucket's window size. + */ + public int MAX_SESSION_COUNT_RESTRICTED = DEFAULT_MAX_SESSION_COUNT_RESTRICTED; + + /** * The maximum number of {@link TimingSession}s that can run within the past * {@link #ALLOWED_TIME_PER_PERIOD_MS}. */ @@ -2087,6 +2162,8 @@ public final class QuotaController extends StateController { KEY_WINDOW_SIZE_FREQUENT_MS, DEFAULT_WINDOW_SIZE_FREQUENT_MS); WINDOW_SIZE_RARE_MS = mParser.getDurationMillis( KEY_WINDOW_SIZE_RARE_MS, DEFAULT_WINDOW_SIZE_RARE_MS); + WINDOW_SIZE_RESTRICTED_MS = mParser.getDurationMillis( + KEY_WINDOW_SIZE_RESTRICTED_MS, DEFAULT_WINDOW_SIZE_RESTRICTED_MS); MAX_EXECUTION_TIME_MS = mParser.getDurationMillis( KEY_MAX_EXECUTION_TIME_MS, DEFAULT_MAX_EXECUTION_TIME_MS); MAX_JOB_COUNT_ACTIVE = mParser.getInt( @@ -2097,6 +2174,8 @@ public final class QuotaController extends StateController { KEY_MAX_JOB_COUNT_FREQUENT, DEFAULT_MAX_JOB_COUNT_FREQUENT); MAX_JOB_COUNT_RARE = mParser.getInt( KEY_MAX_JOB_COUNT_RARE, DEFAULT_MAX_JOB_COUNT_RARE); + MAX_JOB_COUNT_RESTRICTED = mParser.getInt( + KEY_MAX_JOB_COUNT_RESTRICTED, DEFAULT_MAX_JOB_COUNT_RESTRICTED); RATE_LIMITING_WINDOW_MS = mParser.getLong( KEY_RATE_LIMITING_WINDOW_MS, DEFAULT_RATE_LIMITING_WINDOW_MS); MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = mParser.getInt( @@ -2110,6 +2189,8 @@ public final class QuotaController extends StateController { KEY_MAX_SESSION_COUNT_FREQUENT, DEFAULT_MAX_SESSION_COUNT_FREQUENT); MAX_SESSION_COUNT_RARE = mParser.getInt( KEY_MAX_SESSION_COUNT_RARE, DEFAULT_MAX_SESSION_COUNT_RARE); + MAX_SESSION_COUNT_RESTRICTED = mParser.getInt( + KEY_MAX_SESSION_COUNT_RESTRICTED, DEFAULT_MAX_SESSION_COUNT_RESTRICTED); MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = mParser.getInt( KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW, DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW); @@ -2173,6 +2254,13 @@ public final class QuotaController extends StateController { mBucketPeriodsMs[RARE_INDEX] = newRarePeriodMs; changed = true; } + // Fit in the range [allowed time (10 mins), 1 week]. + long newRestrictedPeriodMs = Math.max(mAllowedTimePerPeriodMs, + Math.min(7 * 24 * 60 * MINUTE_IN_MILLIS, WINDOW_SIZE_RESTRICTED_MS)); + if (mBucketPeriodsMs[RESTRICTED_INDEX] != newRestrictedPeriodMs) { + mBucketPeriodsMs[RESTRICTED_INDEX] = newRestrictedPeriodMs; + changed = true; + } long newRateLimitingWindowMs = Math.min(MAX_PERIOD_MS, Math.max(MIN_RATE_LIMITING_WINDOW_MS, RATE_LIMITING_WINDOW_MS)); if (mRateLimitingWindowMs != newRateLimitingWindowMs) { @@ -2206,6 +2294,12 @@ public final class QuotaController extends StateController { mMaxBucketJobCounts[RARE_INDEX] = newRareMaxJobCount; changed = true; } + int newRestrictedMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, + MAX_JOB_COUNT_RESTRICTED); + if (mMaxBucketJobCounts[RESTRICTED_INDEX] != newRestrictedMaxJobCount) { + mMaxBucketJobCounts[RESTRICTED_INDEX] = newRestrictedMaxJobCount; + changed = true; + } int newMaxSessionCountPerRateLimitPeriod = Math.max( MIN_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW, MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW); @@ -2237,6 +2331,11 @@ public final class QuotaController extends StateController { mMaxBucketSessionCounts[RARE_INDEX] = newRareMaxSessionCount; changed = true; } + int newRestrictedMaxSessionCount = Math.max(0, MAX_SESSION_COUNT_RESTRICTED); + if (mMaxBucketSessionCounts[RESTRICTED_INDEX] != newRestrictedMaxSessionCount) { + mMaxBucketSessionCounts[RESTRICTED_INDEX] = newRestrictedMaxSessionCount; + changed = true; + } long newSessionCoalescingDurationMs = Math.min(15 * MINUTE_IN_MILLIS, Math.max(0, TIMING_SESSION_COALESCING_DURATION_MS)); if (mTimingSessionCoalescingDurationMs != newSessionCoalescingDurationMs) { @@ -2266,11 +2365,13 @@ public final class QuotaController extends StateController { pw.printPair(KEY_WINDOW_SIZE_WORKING_MS, WINDOW_SIZE_WORKING_MS).println(); pw.printPair(KEY_WINDOW_SIZE_FREQUENT_MS, WINDOW_SIZE_FREQUENT_MS).println(); pw.printPair(KEY_WINDOW_SIZE_RARE_MS, WINDOW_SIZE_RARE_MS).println(); + pw.printPair(KEY_WINDOW_SIZE_RESTRICTED_MS, WINDOW_SIZE_RESTRICTED_MS).println(); pw.printPair(KEY_MAX_EXECUTION_TIME_MS, MAX_EXECUTION_TIME_MS).println(); pw.printPair(KEY_MAX_JOB_COUNT_ACTIVE, MAX_JOB_COUNT_ACTIVE).println(); pw.printPair(KEY_MAX_JOB_COUNT_WORKING, MAX_JOB_COUNT_WORKING).println(); pw.printPair(KEY_MAX_JOB_COUNT_FREQUENT, MAX_JOB_COUNT_FREQUENT).println(); pw.printPair(KEY_MAX_JOB_COUNT_RARE, MAX_JOB_COUNT_RARE).println(); + pw.printPair(KEY_MAX_JOB_COUNT_RESTRICTED, MAX_JOB_COUNT_RESTRICTED).println(); pw.printPair(KEY_RATE_LIMITING_WINDOW_MS, RATE_LIMITING_WINDOW_MS).println(); pw.printPair(KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW, MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW).println(); @@ -2278,6 +2379,7 @@ public final class QuotaController extends StateController { pw.printPair(KEY_MAX_SESSION_COUNT_WORKING, MAX_SESSION_COUNT_WORKING).println(); pw.printPair(KEY_MAX_SESSION_COUNT_FREQUENT, MAX_SESSION_COUNT_FREQUENT).println(); pw.printPair(KEY_MAX_SESSION_COUNT_RARE, MAX_SESSION_COUNT_RARE).println(); + pw.printPair(KEY_MAX_SESSION_COUNT_RESTRICTED, MAX_SESSION_COUNT_RESTRICTED).println(); pw.printPair(KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW, MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW).println(); pw.printPair(KEY_TIMING_SESSION_COALESCING_DURATION_MS, @@ -2297,6 +2399,8 @@ public final class QuotaController extends StateController { proto.write(ConstantsProto.QuotaController.FREQUENT_WINDOW_SIZE_MS, WINDOW_SIZE_FREQUENT_MS); proto.write(ConstantsProto.QuotaController.RARE_WINDOW_SIZE_MS, WINDOW_SIZE_RARE_MS); + proto.write(ConstantsProto.QuotaController.RESTRICTED_WINDOW_SIZE_MS, + WINDOW_SIZE_RESTRICTED_MS); proto.write(ConstantsProto.QuotaController.MAX_EXECUTION_TIME_MS, MAX_EXECUTION_TIME_MS); proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_ACTIVE, MAX_JOB_COUNT_ACTIVE); @@ -2305,6 +2409,8 @@ public final class QuotaController extends StateController { proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_FREQUENT, MAX_JOB_COUNT_FREQUENT); proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_RARE, MAX_JOB_COUNT_RARE); + proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_RESTRICTED, + MAX_JOB_COUNT_RESTRICTED); proto.write(ConstantsProto.QuotaController.RATE_LIMITING_WINDOW_MS, RATE_LIMITING_WINDOW_MS); proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW, @@ -2317,6 +2423,8 @@ public final class QuotaController extends StateController { MAX_SESSION_COUNT_FREQUENT); proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_RARE, MAX_SESSION_COUNT_RARE); + proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_RESTRICTED, + MAX_SESSION_COUNT_RESTRICTED); proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW, MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW); proto.write(ConstantsProto.QuotaController.TIMING_SESSION_COALESCING_DURATION_MS, diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/RestrictingController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/RestrictingController.java new file mode 100644 index 000000000000..5c637bb92ccc --- /dev/null +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/RestrictingController.java @@ -0,0 +1,41 @@ +/* + * 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.job.controllers; + +import com.android.server.job.JobSchedulerService; + +/** + * Controller that can also handle jobs in the + * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} bucket. + */ +public abstract class RestrictingController extends StateController { + RestrictingController(JobSchedulerService service) { + super(service); + } + + /** + * Start tracking a job that has been added to the + * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} bucket. + */ + public abstract void startTrackingRestrictedJobLocked(JobStatus jobStatus); + + /** + * Stop tracking a job that has been removed from the + * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} bucket. + */ + public abstract void stopTrackingRestrictedJobLocked(JobStatus jobStatus); +} diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java index b9df30aa4d95..9d6e012da89b 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java @@ -25,8 +25,11 @@ import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_USER_INTERACT import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_NEVER; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE; +import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET; +import static com.android.server.usage.AppStandbyController.isUserUsage; + import android.app.usage.AppStandbyInfo; import android.app.usage.UsageStatsManager; import android.os.SystemClock; @@ -81,6 +84,8 @@ public class AppIdleHistory { private static final String ATTR_SCREEN_IDLE = "screenIdleTime"; // Elapsed timebase time when app was last used private static final String ATTR_ELAPSED_IDLE = "elapsedIdleTime"; + // Elapsed timebase time when app was last used by the user + private static final String ATTR_LAST_USED_BY_USER_ELAPSED = "lastUsedByUserElapsedTime"; // Elapsed timebase time when the app bucket was last predicted externally private static final String ATTR_LAST_PREDICTED_TIME = "lastPredictedTime"; // The standby bucket for the app @@ -93,6 +98,12 @@ public class AppIdleHistory { private static final String ATTR_BUCKET_ACTIVE_TIMEOUT_TIME = "activeTimeoutTime"; // The time when the forced working_set state can be overridden. private static final String ATTR_BUCKET_WORKING_SET_TIMEOUT_TIME = "workingSetTimeoutTime"; + // Elapsed timebase time when the app was last marked for restriction. + private static final String ATTR_LAST_RESTRICTION_ATTEMPT_ELAPSED = + "lastRestrictionAttemptElapsedTime"; + // Reason why the app was last marked for restriction. + private static final String ATTR_LAST_RESTRICTION_ATTEMPT_REASON = + "lastRestrictionAttemptReason"; // device on time = mElapsedDuration + (timeNow - mElapsedSnapshot) private long mElapsedSnapshot; // Elapsed time snapshot when last write of mDeviceOnDuration @@ -107,8 +118,10 @@ public class AppIdleHistory { private boolean mScreenOn; static class AppUsageHistory { - // Last used time using elapsed timebase + // Last used time (including system usage), using elapsed timebase long lastUsedElapsedTime; + // Last time the user used the app, using elapsed timebase + long lastUsedByUserElapsedTime; // Last used time using screen_on timebase long lastUsedScreenTime; // Last predicted time using elapsed timebase @@ -136,6 +149,10 @@ public class AppIdleHistory { // under any active state timeout, so that it becomes applicable after the active state // timeout expires. long bucketWorkingSetTimeoutTime; + // The last time an agent attempted to put the app into the RESTRICTED bucket. + long lastRestrictAttemptElapsedTime; + // The last reason the app was marked to be put into the RESTRICTED bucket. + int lastRestrictReason; } AppIdleHistory(File storageDir, long elapsedRealtime) { @@ -229,25 +246,37 @@ public class AppIdleHistory { */ public AppUsageHistory reportUsage(AppUsageHistory appUsageHistory, String packageName, int newBucket, int usageReason, long elapsedRealtime, long timeout) { - // Set the timeout if applicable - if (timeout > elapsedRealtime) { - // Convert to elapsed timebase - final long timeoutTime = mElapsedDuration + (timeout - mElapsedSnapshot); - if (newBucket == STANDBY_BUCKET_ACTIVE) { - appUsageHistory.bucketActiveTimeoutTime = Math.max(timeoutTime, - appUsageHistory.bucketActiveTimeoutTime); - } else if (newBucket == STANDBY_BUCKET_WORKING_SET) { - appUsageHistory.bucketWorkingSetTimeoutTime = Math.max(timeoutTime, - appUsageHistory.bucketWorkingSetTimeoutTime); - } else { - throw new IllegalArgumentException("Cannot set a timeout on bucket=" + - newBucket); + int bucketingReason = REASON_MAIN_USAGE | usageReason; + final boolean isUserUsage = isUserUsage(bucketingReason); + + if (appUsageHistory.currentBucket == STANDBY_BUCKET_RESTRICTED && !isUserUsage) { + // Only user usage should bring an app out of the RESTRICTED bucket. + newBucket = STANDBY_BUCKET_RESTRICTED; + bucketingReason = appUsageHistory.bucketingReason; + } else { + // Set the timeout if applicable + if (timeout > elapsedRealtime) { + // Convert to elapsed timebase + final long timeoutTime = mElapsedDuration + (timeout - mElapsedSnapshot); + if (newBucket == STANDBY_BUCKET_ACTIVE) { + appUsageHistory.bucketActiveTimeoutTime = Math.max(timeoutTime, + appUsageHistory.bucketActiveTimeoutTime); + } else if (newBucket == STANDBY_BUCKET_WORKING_SET) { + appUsageHistory.bucketWorkingSetTimeoutTime = Math.max(timeoutTime, + appUsageHistory.bucketWorkingSetTimeoutTime); + } else { + throw new IllegalArgumentException("Cannot set a timeout on bucket=" + + newBucket); + } } } if (elapsedRealtime != 0) { appUsageHistory.lastUsedElapsedTime = mElapsedDuration + (elapsedRealtime - mElapsedSnapshot); + if (isUserUsage) { + appUsageHistory.lastUsedByUserElapsedTime = appUsageHistory.lastUsedElapsedTime; + } appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime); } @@ -259,7 +288,7 @@ public class AppIdleHistory { + ", reason=0x0" + Integer.toHexString(appUsageHistory.bucketingReason)); } } - appUsageHistory.bucketingReason = REASON_MAIN_USAGE | usageReason; + appUsageHistory.bucketingReason = bucketingReason; return appUsageHistory; } @@ -386,6 +415,24 @@ public class AppIdleHistory { } /** + * Notes an attempt to put the app in the {@link UsageStatsManager#STANDBY_BUCKET_RESTRICTED} + * bucket. + * + * @param packageName The package name of the app that is being restricted + * @param userId The ID of the user in which the app is being restricted + * @param elapsedRealtime The time the attempt was made, in the (unadjusted) elapsed realtime + * timebase + * @param reason The reason for the restriction attempt + */ + void noteRestrictionAttempt(String packageName, int userId, long elapsedRealtime, int reason) { + ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); + AppUsageHistory appUsageHistory = + getPackageHistory(userHistory, packageName, elapsedRealtime, true); + appUsageHistory.lastRestrictAttemptElapsedTime = getElapsedTime(elapsedRealtime); + appUsageHistory.lastRestrictReason = reason; + } + + /** * Returns the time since the last job was run for this app. This can be larger than the * current elapsedRealtime, in case it happened before boot or a really large value if no jobs * were ever run. @@ -547,6 +594,9 @@ public class AppIdleHistory { AppUsageHistory appUsageHistory = new AppUsageHistory(); appUsageHistory.lastUsedElapsedTime = Long.parseLong(parser.getAttributeValue(null, ATTR_ELAPSED_IDLE)); + appUsageHistory.lastUsedByUserElapsedTime = getLongValue(parser, + ATTR_LAST_USED_BY_USER_ELAPSED, + appUsageHistory.lastUsedElapsedTime); appUsageHistory.lastUsedScreenTime = Long.parseLong(parser.getAttributeValue(null, ATTR_SCREEN_IDLE)); appUsageHistory.lastPredictedTime = getLongValue(parser, @@ -570,6 +620,19 @@ public class AppIdleHistory { appUsageHistory.bucketingReason = Integer.parseInt(bucketingReason, 16); } catch (NumberFormatException nfe) { + Slog.wtf(TAG, "Unable to read bucketing reason", nfe); + } + } + appUsageHistory.lastRestrictAttemptElapsedTime = + getLongValue(parser, ATTR_LAST_RESTRICTION_ATTEMPT_ELAPSED, 0); + String lastRestrictReason = parser.getAttributeValue( + null, ATTR_LAST_RESTRICTION_ATTEMPT_REASON); + if (lastRestrictReason != null) { + try { + appUsageHistory.lastRestrictReason = + Integer.parseInt(lastRestrictReason, 16); + } catch (NumberFormatException nfe) { + Slog.wtf(TAG, "Unable to read last restrict reason", nfe); } } appUsageHistory.lastInformedBucket = -1; @@ -618,6 +681,8 @@ public class AppIdleHistory { xml.attribute(null, ATTR_NAME, packageName); xml.attribute(null, ATTR_ELAPSED_IDLE, Long.toString(history.lastUsedElapsedTime)); + xml.attribute(null, ATTR_LAST_USED_BY_USER_ELAPSED, + Long.toString(history.lastUsedByUserElapsedTime)); xml.attribute(null, ATTR_SCREEN_IDLE, Long.toString(history.lastUsedScreenTime)); xml.attribute(null, ATTR_LAST_PREDICTED_TIME, @@ -638,6 +703,12 @@ public class AppIdleHistory { xml.attribute(null, ATTR_LAST_RUN_JOB_TIME, Long.toString(history .lastJobRunTime)); } + if (history.lastRestrictAttemptElapsedTime > 0) { + xml.attribute(null, ATTR_LAST_RESTRICTION_ATTEMPT_ELAPSED, + Long.toString(history.lastRestrictAttemptElapsedTime)); + } + xml.attribute(null, ATTR_LAST_RESTRICTION_ATTEMPT_REASON, + Integer.toHexString(history.lastRestrictReason)); xml.endTag(null, TAG_PACKAGE); } @@ -672,6 +743,9 @@ public class AppIdleHistory { + UsageStatsManager.reasonToString(appUsageHistory.bucketingReason)); idpw.print(" used="); TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastUsedElapsedTime, idpw); + idpw.print(" usedByUser="); + TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastUsedByUserElapsedTime, + idpw); idpw.print(" usedScr="); TimeUtils.formatDuration(screenOnTime - appUsageHistory.lastUsedScreenTime, idpw); idpw.print(" lastPred="); @@ -684,6 +758,13 @@ public class AppIdleHistory { idpw); idpw.print(" lastJob="); TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastJobRunTime, idpw); + if (appUsageHistory.lastRestrictAttemptElapsedTime > 0) { + idpw.print(" lastRestrictAttempt="); + TimeUtils.formatDuration( + totalElapsedTime - appUsageHistory.lastRestrictAttemptElapsedTime, idpw); + idpw.print(" lastRestrictReason=" + + UsageStatsManager.reasonToString(appUsageHistory.lastRestrictReason)); + } idpw.print(" idle=" + (isIdle(packageName, userId, elapsedRealtime) ? "y" : "n")); idpw.println(); } diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index eb0b54b1d9fc..b1b8fba78ab9 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -23,6 +23,7 @@ import static android.app.usage.UsageStatsManager.REASON_MAIN_MASK; import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED; import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT; import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE; +import static android.app.usage.UsageStatsManager.REASON_SUB_MASK; import static android.app.usage.UsageStatsManager.REASON_SUB_PREDICTED_RESTORED; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_ACTIVE_TIMEOUT; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_EXEMPTED_SYNC_SCHEDULED_DOZE; @@ -44,6 +45,7 @@ import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_EXEMPTED; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_NEVER; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE; +import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET; import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; @@ -73,6 +75,7 @@ import android.net.Network; import android.net.NetworkRequest; import android.net.NetworkScoreManager; import android.os.BatteryStats; +import android.os.Build; import android.os.Environment; import android.os.Handler; import android.os.IDeviceIdleController; @@ -93,7 +96,9 @@ import android.util.SparseArray; import android.util.SparseIntArray; import android.util.TimeUtils; import android.view.Display; +import android.widget.Toast; +import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; @@ -124,7 +129,7 @@ import java.util.concurrent.CountDownLatch; public class AppStandbyController implements AppStandbyInternal { private static final String TAG = "AppStandbyController"; - static final boolean DEBUG = false; + static final boolean DEBUG = true; static final boolean COMPRESS_TIME = false; private static final long ONE_MINUTE = 60 * 1000; @@ -615,6 +620,16 @@ public class AppStandbyController implements AppStandbyInternal { Slog.d(TAG, " Keeping at WORKING_SET due to min timeout"); } } + + if (app.lastRestrictAttemptElapsedTime > app.lastUsedByUserElapsedTime + && elapsedTimeAdjusted - app.lastUsedByUserElapsedTime + >= mInjector.getRestrictedBucketDelayMs()) { + newBucket = STANDBY_BUCKET_RESTRICTED; + reason = app.lastRestrictReason; + if (DEBUG) { + Slog.d(TAG, "Bringing down to RESTRICTED due to timeout"); + } + } if (DEBUG) { Slog.d(TAG, " Old bucket=" + oldBucket + ", newBucket=" + newBucket); @@ -733,15 +748,16 @@ public class AppStandbyController implements AppStandbyInternal { elapsedRealtime, elapsedRealtime + mStrongUsageTimeoutMillis); nextCheckTime = mStrongUsageTimeoutMillis; } - mHandler.sendMessageDelayed(mHandler.obtainMessage - (MSG_CHECK_PACKAGE_IDLE_STATE, userId, -1, pkg), - nextCheckTime); - final boolean userStartedInteracting = - appHistory.currentBucket == STANDBY_BUCKET_ACTIVE && - prevBucket != appHistory.currentBucket && - (prevBucketReason & REASON_MAIN_MASK) != REASON_MAIN_USAGE; - maybeInformListeners(pkg, userId, elapsedRealtime, - appHistory.currentBucket, reason, userStartedInteracting); + if (appHistory.currentBucket != prevBucket) { + mHandler.sendMessageDelayed( + mHandler.obtainMessage(MSG_CHECK_PACKAGE_IDLE_STATE, userId, -1, pkg), + nextCheckTime); + final boolean userStartedInteracting = + appHistory.currentBucket == STANDBY_BUCKET_ACTIVE + && (prevBucketReason & REASON_MAIN_MASK) != REASON_MAIN_USAGE; + maybeInformListeners(pkg, userId, elapsedRealtime, + appHistory.currentBucket, reason, userStartedInteracting); + } if (previouslyIdle) { notifyBatteryStats(pkg, userId, false); @@ -923,6 +939,15 @@ public class AppStandbyController implements AppStandbyInternal { } } + static boolean isUserUsage(int reason) { + if ((reason & REASON_MAIN_MASK) == REASON_MAIN_USAGE) { + final int subReason = reason & REASON_SUB_MASK; + return subReason == REASON_SUB_USAGE_USER_INTERACTION + || subReason == REASON_SUB_USAGE_MOVE_TO_FOREGROUND; + } + return false; + } + @Override public int[] getIdleUidsForUser(int userId) { if (!mAppIdleEnabled) { @@ -1017,6 +1042,20 @@ public class AppStandbyController implements AppStandbyInternal { } @Override + public void restrictApp(@NonNull String packageName, int userId, int restrictReason) { + // If the package is not installed, don't allow the bucket to be set. + if (!mInjector.isPackageInstalled(packageName, 0, userId)) { + Slog.e(TAG, "Tried to restrict uninstalled app: " + packageName); + return; + } + + final int reason = REASON_MAIN_FORCED_BY_SYSTEM | (REASON_SUB_MASK & restrictReason); + final long nowElapsed = mInjector.elapsedRealtime(); + setAppStandbyBucket(packageName, userId, STANDBY_BUCKET_RESTRICTED, reason, + nowElapsed, false); + } + + @Override public void setAppStandbyBucket(@NonNull String packageName, int bucket, int userId, int callingUid, int callingPid) { setAppStandbyBuckets( @@ -1080,6 +1119,7 @@ public class AppStandbyController implements AppStandbyInternal { synchronized (mAppIdleLock) { // If the package is not installed, don't allow the bucket to be set. if (!mInjector.isPackageInstalled(packageName, 0, userId)) { + Slog.e(TAG, "Tried to set bucket of uninstalled app: " + packageName); return; } AppIdleHistory.AppUsageHistory app = mAppIdleHistory.getAppUsageHistory(packageName, @@ -1089,8 +1129,9 @@ public class AppStandbyController implements AppStandbyInternal { // Don't allow changing bucket if higher than ACTIVE if (app.currentBucket < STANDBY_BUCKET_ACTIVE) return; - // Don't allow prediction to change from/to NEVER + // Don't allow prediction to change from/to NEVER or from RESTRICTED. if ((app.currentBucket == STANDBY_BUCKET_NEVER + || app.currentBucket == STANDBY_BUCKET_RESTRICTED || newBucket == STANDBY_BUCKET_NEVER) && predicted) { return; @@ -1103,6 +1144,50 @@ public class AppStandbyController implements AppStandbyInternal { return; } + final boolean isForcedByUser = + (reason & REASON_MAIN_MASK) == REASON_MAIN_FORCED_BY_USER; + + // If the current bucket is RESTRICTED, only user force or usage should bring it out. + if (app.currentBucket == STANDBY_BUCKET_RESTRICTED && !isUserUsage(reason) + && !isForcedByUser) { + return; + } + + if (newBucket == STANDBY_BUCKET_RESTRICTED) { + mAppIdleHistory + .noteRestrictionAttempt(packageName, userId, elapsedRealtime, reason); + + if (isForcedByUser) { + // Only user force can bypass the delay restriction. If the user forced the + // app into the RESTRICTED bucket, then a toast confirming the action + // shouldn't be surprising. + if (Build.IS_DEBUGGABLE) { + Toast.makeText(mContext, + // Since AppStandbyController sits low in the lock hierarchy, + // make sure not to call out with the lock held. + mHandler.getLooper(), + mContext.getResources().getString( + R.string.as_app_forced_to_restricted_bucket, packageName), + Toast.LENGTH_SHORT) + .show(); + } else { + Slog.i(TAG, packageName + " restricted by user"); + } + } else { + final long timeUntilRestrictPossibleMs = app.lastUsedByUserElapsedTime + + mInjector.getRestrictedBucketDelayMs() - elapsedRealtime; + if (timeUntilRestrictPossibleMs > 0) { + Slog.w(TAG, "Tried to restrict recently used app: " + packageName + + " due to " + reason); + mHandler.sendMessageDelayed( + mHandler.obtainMessage( + MSG_CHECK_PACKAGE_IDLE_STATE, userId, -1, packageName), + timeUntilRestrictPossibleMs); + return; + } + } + } + // If the bucket is required to stay in a higher state for a specified duration, don't // override unless the duration has passed if (predicted) { @@ -1435,6 +1520,12 @@ public class AppStandbyController implements AppStandbyInternal { private DisplayManager mDisplayManager; private PowerManager mPowerManager; int mBootPhase; + /** + * The minimum amount of time required since the last user interaction before an app can be + * placed in the RESTRICTED bucket. + */ + // TODO: make configurable via DeviceConfig + private long mRestrictedBucketDelayMs = ONE_DAY; Injector(Context context, Looper looper) { mContext = context; @@ -1459,6 +1550,12 @@ public class AppStandbyController implements AppStandbyInternal { mDisplayManager = (DisplayManager) mContext.getSystemService( Context.DISPLAY_SERVICE); mPowerManager = mContext.getSystemService(PowerManager.class); + + final ActivityManager activityManager = + (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); + if (activityManager.isLowRamDevice() || ActivityManager.isSmallBatteryDevice()) { + mRestrictedBucketDelayMs = 12 * ONE_HOUR; + } } mBootPhase = phase; } @@ -1498,6 +1595,10 @@ public class AppStandbyController implements AppStandbyInternal { return Environment.getDataSystemDirectory(); } + long getRestrictedBucketDelayMs() { + return mRestrictedBucketDelayMs; + } + void noteEvent(int event, String packageName, int uid) throws RemoteException { mBatteryStats.noteEvent(event, packageName, uid); } diff --git a/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java b/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java index 51b911a94edc..1dbad456760c 100644 --- a/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java +++ b/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java @@ -18,6 +18,7 @@ package com.android.permission.persistence; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.ApexContext; import android.os.UserHandle; import android.util.ArrayMap; import android.util.AtomicFile; @@ -48,6 +49,8 @@ public class RuntimePermissionsPersistenceImpl implements RuntimePermissionsPers private static final String LOG_TAG = RuntimePermissionsPersistenceImpl.class.getSimpleName(); + private static final String APEX_MODULE_NAME = "com.android.permission"; + private static final String RUNTIME_PERMISSIONS_FILE_NAME = "runtime-permissions.xml"; private static final String TAG_PACKAGE = "package"; @@ -253,9 +256,8 @@ public class RuntimePermissionsPersistenceImpl implements RuntimePermissionsPers @NonNull private static File getFile(@NonNull UserHandle user) { - // TODO: Use an API for this. - File dataDirectory = new File("/data/misc_de/" + user.getIdentifier() - + "/apexdata/com.android.permission"); + ApexContext apexContext = ApexContext.getApexContext(APEX_MODULE_NAME); + File dataDirectory = apexContext.getDeviceProtectedDataDirForUser(user); return new File(dataDirectory, RUNTIME_PERMISSIONS_FILE_NAME); } } diff --git a/apex/permission/service/java/com/android/role/persistence/RolesPersistenceImpl.java b/apex/permission/service/java/com/android/role/persistence/RolesPersistenceImpl.java index 5061742f4c58..06fad77c495c 100644 --- a/apex/permission/service/java/com/android/role/persistence/RolesPersistenceImpl.java +++ b/apex/permission/service/java/com/android/role/persistence/RolesPersistenceImpl.java @@ -18,6 +18,7 @@ package com.android.role.persistence; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.ApexContext; import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; @@ -50,6 +51,8 @@ public class RolesPersistenceImpl implements RolesPersistence { private static final String LOG_TAG = RolesPersistenceImpl.class.getSimpleName(); + private static final String APEX_MODULE_NAME = "com.android.permission"; + private static final String ROLES_FILE_NAME = "roles.xml"; private static final String TAG_ROLES = "roles"; @@ -209,9 +212,8 @@ public class RolesPersistenceImpl implements RolesPersistence { @NonNull private static File getFile(@NonNull UserHandle user) { - // TODO: Use an API for this. - File dataDirectory = new File("/data/misc_de/" + user.getIdentifier() - + "/apexdata/com.android.permission"); + ApexContext apexContext = ApexContext.getApexContext(APEX_MODULE_NAME); + File dataDirectory = apexContext.getDeviceProtectedDataDirForUser(user); return new File(dataDirectory, ROLES_FILE_NAME); } } diff --git a/api/current.txt b/api/current.txt index bf5d24d12615..cbea597ac42f 100644 --- a/api/current.txt +++ b/api/current.txt @@ -8035,6 +8035,7 @@ package android.app.usage { field public static final int STANDBY_BUCKET_ACTIVE = 10; // 0xa field public static final int STANDBY_BUCKET_FREQUENT = 30; // 0x1e field public static final int STANDBY_BUCKET_RARE = 40; // 0x28 + field public static final int STANDBY_BUCKET_RESTRICTED = 45; // 0x2d field public static final int STANDBY_BUCKET_WORKING_SET = 20; // 0x14 } @@ -9969,6 +9970,7 @@ package android.content { method public abstract android.content.Context createDisplayContext(@NonNull android.view.Display); method @NonNull public android.content.Context createFeatureContext(@Nullable String); method public abstract android.content.Context createPackageContext(String, int) throws android.content.pm.PackageManager.NameNotFoundException; + method @NonNull public android.content.Context createWindowContext(int); method public abstract String[] databaseList(); method public abstract boolean deleteDatabase(String); method public abstract boolean deleteFile(String); @@ -9993,6 +9995,7 @@ package android.content { method public abstract java.io.File getDataDir(); method public abstract java.io.File getDatabasePath(String); method public abstract java.io.File getDir(String, int); + method @Nullable public android.view.Display getDisplay(); method @Nullable public final android.graphics.drawable.Drawable getDrawable(@DrawableRes int); method @Nullable public abstract java.io.File getExternalCacheDir(); method public abstract java.io.File[] getExternalCacheDirs(); @@ -10105,6 +10108,7 @@ package android.content { field public static final String CARRIER_CONFIG_SERVICE = "carrier_config"; field public static final String CLIPBOARD_SERVICE = "clipboard"; field public static final String COMPANION_DEVICE_SERVICE = "companiondevice"; + field public static final String CONNECTIVITY_DIAGNOSTICS_SERVICE = "connectivity_diagnostics"; field public static final String CONNECTIVITY_SERVICE = "connectivity"; field public static final String CONSUMER_IR_SERVICE = "consumer_ir"; field public static final int CONTEXT_IGNORE_SECURITY = 2; // 0x2 @@ -11774,6 +11778,7 @@ package android.content.pm { method @Nullable public CharSequence getAppLabel(); method @Nullable public String getAppPackageName(); method @NonNull public int[] getChildSessionIds(); + method public long getCreatedMillis(); method public int getInstallLocation(); method public int getInstallReason(); method @Nullable public String getInstallerPackageName(); @@ -11890,7 +11895,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 @NonNull public CharSequence getBackgroundPermissionOptionLabel(); 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(); @@ -12081,6 +12086,7 @@ package android.content.pm { field public static final String FEATURE_VR_HEADTRACKING = "android.hardware.vr.headtracking"; field @Deprecated public static final String FEATURE_VR_MODE = "android.software.vr.mode"; field public static final String FEATURE_VR_MODE_HIGH_PERFORMANCE = "android.hardware.vr.high_performance"; + field public static final String FEATURE_VULKAN_DEQP_LEVEL = "android.software.vulkan.deqp.level"; field public static final String FEATURE_VULKAN_HARDWARE_COMPUTE = "android.hardware.vulkan.compute"; field public static final String FEATURE_VULKAN_HARDWARE_LEVEL = "android.hardware.vulkan.level"; field public static final String FEATURE_VULKAN_HARDWARE_VERSION = "android.hardware.vulkan.version"; @@ -23405,12 +23411,12 @@ package android.location { method public int getAccumulatedDeltaRangeState(); method public double getAccumulatedDeltaRangeUncertaintyMeters(); method public double getAutomaticGainControlLevelDb(); - method @FloatRange(from=0, to=50) public double getBasebandCn0DbHz(); + method @FloatRange(from=0, to=63) public double getBasebandCn0DbHz(); method @Deprecated public long getCarrierCycles(); method public float getCarrierFrequencyHz(); method @Deprecated public double getCarrierPhase(); method @Deprecated public double getCarrierPhaseUncertainty(); - method public double getCn0DbHz(); + method @FloatRange(from=0, to=63) public double getCn0DbHz(); method @NonNull public String getCodeType(); method public int getConstellationType(); method public int getMultipathIndicator(); @@ -23492,6 +23498,8 @@ package android.location { field public static final int STATUS_PARITY_PASSED = 1; // 0x1 field public static final int STATUS_PARITY_REBUILT = 2; // 0x2 field public static final int STATUS_UNKNOWN = 0; // 0x0 + field public static final int TYPE_BDS_CNAV1 = 1283; // 0x503 + field public static final int TYPE_BDS_CNAV2 = 1284; // 0x504 field public static final int TYPE_BDS_D1 = 1281; // 0x501 field public static final int TYPE_BDS_D2 = 1282; // 0x502 field public static final int TYPE_GAL_F = 1538; // 0x602 @@ -23501,6 +23509,9 @@ package android.location { field public static final int TYPE_GPS_L1CA = 257; // 0x101 field public static final int TYPE_GPS_L2CNAV = 258; // 0x102 field public static final int TYPE_GPS_L5CNAV = 259; // 0x103 + field public static final int TYPE_IRN_L5CA = 1793; // 0x701 + field public static final int TYPE_QZS_L1CA = 1025; // 0x401 + field public static final int TYPE_SBS = 513; // 0x201 field public static final int TYPE_UNKNOWN = 0; // 0x0 } @@ -23827,6 +23838,8 @@ package android.media { method @NonNull public int[] getChannelCounts(); method @NonNull public int[] getChannelIndexMasks(); method @NonNull public int[] getChannelMasks(); + method @NonNull public int[] getEncapsulationMetadataTypes(); + method @NonNull public int[] getEncapsulationModes(); method @NonNull public int[] getEncodings(); method public int getId(); method public CharSequence getProductName(); @@ -24184,8 +24197,10 @@ package android.media { public final class AudioPlaybackCaptureConfiguration { method @NonNull public int[] getExcludeUids(); method @NonNull public int[] getExcludeUsages(); + method @NonNull public int[] getExcludeUserIds(); method @NonNull public int[] getMatchingUids(); method @NonNull public int[] getMatchingUsages(); + method @NonNull public int[] getMatchingUserIds(); method @NonNull public android.media.projection.MediaProjection getMediaProjection(); } @@ -24193,9 +24208,11 @@ package android.media { ctor public AudioPlaybackCaptureConfiguration.Builder(@NonNull android.media.projection.MediaProjection); method @NonNull public android.media.AudioPlaybackCaptureConfiguration.Builder addMatchingUid(int); method @NonNull public android.media.AudioPlaybackCaptureConfiguration.Builder addMatchingUsage(int); + method @NonNull public android.media.AudioPlaybackCaptureConfiguration.Builder addMatchingUserId(int); method @NonNull public android.media.AudioPlaybackCaptureConfiguration build(); method @NonNull public android.media.AudioPlaybackCaptureConfiguration.Builder excludeUid(int); method @NonNull public android.media.AudioPlaybackCaptureConfiguration.Builder excludeUsage(int); + method @NonNull public android.media.AudioPlaybackCaptureConfiguration.Builder excludeUserId(int); } public final class AudioPlaybackConfiguration implements android.os.Parcelable { @@ -24374,12 +24391,14 @@ package android.media { method protected void finalize(); method public void flush(); method @NonNull public android.media.AudioAttributes getAudioAttributes(); + method public float getAudioDescriptionMixLeveldB(); method public int getAudioFormat(); method public int getAudioSessionId(); method @IntRange(from=0) public int getBufferCapacityInFrames(); method @IntRange(from=0) public int getBufferSizeInFrames(); method public int getChannelConfiguration(); method public int getChannelCount(); + method public int getDualMonoMode(); method @NonNull public android.media.AudioFormat getFormat(); method public static float getMaxVolume(); method public android.os.PersistableBundle getMetrics(); @@ -24413,8 +24432,10 @@ package android.media { method public void removeOnCodecFormatChangedListener(@NonNull android.media.AudioTrack.OnCodecFormatChangedListener); method public void removeOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener); method @Deprecated public void removeOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener); + method public boolean setAudioDescriptionMixLeveldB(@FloatRange(to=48.0f, toInclusive=true) float); method public int setAuxEffectSendLevel(@FloatRange(from=0.0) float); method public int setBufferSizeInFrames(@IntRange(from=0) int); + method public boolean setDualMonoMode(int); method public int setLoopPoints(@IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0xffffffff) int); method public int setNotificationMarkerPosition(int); method public void setOffloadDelayPadding(@IntRange(from=0) int, @IntRange(from=0) int); @@ -24439,6 +24460,12 @@ package android.media { method public int write(@NonNull float[], int, int, int); method public int write(@NonNull java.nio.ByteBuffer, int, int); method public int write(@NonNull java.nio.ByteBuffer, int, int, long); + field public static final int DUAL_MONO_MODE_LL = 2; // 0x2 + field public static final int DUAL_MONO_MODE_LR = 1; // 0x1 + field public static final int DUAL_MONO_MODE_OFF = 0; // 0x0 + field public static final int DUAL_MONO_MODE_RR = 3; // 0x3 + field public static final int ENCAPSULATION_METADATA_TYPE_DVB_AD_DESCRIPTOR = 2; // 0x2 + field public static final int ENCAPSULATION_METADATA_TYPE_FRAMEWORK_TUNER = 1; // 0x1 field public static final int ENCAPSULATION_MODE_ELEMENTARY_STREAM = 1; // 0x1 field public static final int ENCAPSULATION_MODE_HANDLE = 2; // 0x2 field public static final int ENCAPSULATION_MODE_NONE = 0; // 0x0 @@ -24988,7 +25015,9 @@ package android.media { method @Deprecated @NonNull public java.nio.ByteBuffer[] getOutputBuffers(); method @NonNull public android.media.MediaFormat getOutputFormat(); method @NonNull public android.media.MediaFormat getOutputFormat(int); + method @NonNull public android.media.MediaCodec.OutputFrame getOutputFrame(int); method @Nullable public android.media.Image getOutputImage(int); + method @NonNull public android.media.MediaCodec.QueueRequest getQueueRequest(int); method public void queueInputBuffer(int, int, int, long, int) throws android.media.MediaCodec.CryptoException; method public void queueSecureInputBuffer(int, int, @NonNull android.media.MediaCodec.CryptoInfo, long, int) throws android.media.MediaCodec.CryptoException; method public void release(); @@ -25012,6 +25041,7 @@ package android.media { field public static final int BUFFER_FLAG_PARTIAL_FRAME = 8; // 0x8 field @Deprecated public static final int BUFFER_FLAG_SYNC_FRAME = 1; // 0x1 field public static final int CONFIGURE_FLAG_ENCODE = 1; // 0x1 + field public static final int CONFIGURE_FLAG_USE_BLOCK_MODEL = 2; // 0x2 field public static final int CRYPTO_MODE_AES_CBC = 2; // 0x2 field public static final int CRYPTO_MODE_AES_CTR = 1; // 0x1 field public static final int CRYPTO_MODE_UNENCRYPTED = 0; // 0x0 @@ -25088,6 +25118,24 @@ package android.media { method public void set(int, int); } + public static final class MediaCodec.GraphicBlock { + method protected void finalize(); + method public static boolean isCodecCopyFreeCompatible(@NonNull String[]); + method public boolean isMappable(); + method @NonNull public android.media.Image map(); + method @NonNull public static android.media.MediaCodec.GraphicBlock obtain(int, int, int, long, @NonNull String[]); + method public void recycle(); + } + + public static final class MediaCodec.LinearBlock { + method protected void finalize(); + method public static boolean isCodecCopyFreeCompatible(@NonNull String[]); + method public boolean isMappable(); + method @NonNull public java.nio.ByteBuffer map(); + method @Nullable public static android.media.MediaCodec.LinearBlock obtain(int, @NonNull String[]); + method public void recycle(); + } + public static final class MediaCodec.MetricsConstants { field public static final String CODEC = "android.media.mediacodec.codec"; field public static final String ENCODER = "android.media.mediacodec.encoder"; @@ -25105,6 +25153,27 @@ package android.media { method public void onFrameRendered(@NonNull android.media.MediaCodec, long, long); } + public static final class MediaCodec.OutputFrame { + method public void getChangedKeys(@NonNull java.util.Set<java.lang.String>); + method public int getFlags(); + method @NonNull public android.media.MediaFormat getFormat(); + method @Nullable public android.media.MediaCodec.GraphicBlock getGraphicBlock(); + method @Nullable public android.media.MediaCodec.LinearBlock getLinearBlock(); + method public long getPresentationTimeUs(); + } + + public final class MediaCodec.QueueRequest { + method public void queue(); + method @NonNull public android.media.MediaCodec.QueueRequest setByteBufferParameter(@NonNull String, @NonNull java.nio.ByteBuffer); + method @NonNull public android.media.MediaCodec.QueueRequest setEncryptedLinearBlock(@NonNull android.media.MediaCodec.LinearBlock, int, @NonNull android.media.MediaCodec.CryptoInfo, long, int); + method @NonNull public android.media.MediaCodec.QueueRequest setFloatParameter(@NonNull String, float); + method @NonNull public android.media.MediaCodec.QueueRequest setGraphicBlock(@NonNull android.media.MediaCodec.GraphicBlock, long, int); + method @NonNull public android.media.MediaCodec.QueueRequest setIntegerParameter(@NonNull String, int); + method @NonNull public android.media.MediaCodec.QueueRequest setLinearBlock(@NonNull android.media.MediaCodec.LinearBlock, int, int, long, int); + method @NonNull public android.media.MediaCodec.QueueRequest setLongParameter(@NonNull String, long); + method @NonNull public android.media.MediaCodec.QueueRequest setStringParameter(@NonNull String, @NonNull String); + } + public final class MediaCodecInfo { method @NonNull public String getCanonicalName(); method public android.media.MediaCodecInfo.CodecCapabilities getCapabilitiesForType(String); @@ -25898,6 +25967,7 @@ package android.media { field public static final String KEY_GRID_COLUMNS = "grid-cols"; field public static final String KEY_GRID_ROWS = "grid-rows"; field public static final String KEY_HAPTIC_CHANNEL_COUNT = "haptic-channel-count"; + field public static final String KEY_HARDWARE_AV_SYNC_ID = "hw-av-sync-id"; field public static final String KEY_HDR10_PLUS_INFO = "hdr10-plus-info"; field public static final String KEY_HDR_STATIC_INFO = "hdr-static-info"; field public static final String KEY_HEIGHT = "height"; @@ -29495,8 +29565,6 @@ package android.net { public class ConnectivityDiagnosticsManager { method public void registerConnectivityDiagnosticsCallback(@NonNull android.net.NetworkRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback); method public void unregisterConnectivityDiagnosticsCallback(@NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback); - field public static final int DETECTION_METHOD_DNS_EVENTS = 1; // 0x1 - field public static final int DETECTION_METHOD_TCP_METRICS = 2; // 0x2 } public abstract static class ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback { @@ -29506,21 +29574,29 @@ package android.net { method public void onNetworkConnectivityReported(@NonNull android.net.Network, boolean); } - public static class ConnectivityDiagnosticsManager.ConnectivityReport { + public static final class ConnectivityDiagnosticsManager.ConnectivityReport implements android.os.Parcelable { ctor public ConnectivityDiagnosticsManager.ConnectivityReport(@NonNull android.net.Network, long, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkCapabilities, @NonNull android.os.PersistableBundle); - field @NonNull public final android.os.PersistableBundle additionalInfo; - field @NonNull public final android.net.LinkProperties linkProperties; - field @NonNull public final android.net.Network network; - field @NonNull public final android.net.NetworkCapabilities networkCapabilities; - field public final long reportTimestamp; + method public int describeContents(); + method @NonNull public android.os.PersistableBundle getAdditionalInfo(); + method @NonNull public android.net.LinkProperties getLinkProperties(); + method @NonNull public android.net.Network getNetwork(); + method @NonNull public android.net.NetworkCapabilities getNetworkCapabilities(); + method public long getReportTimestamp(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.ConnectivityDiagnosticsManager.ConnectivityReport> CREATOR; } - public static class ConnectivityDiagnosticsManager.DataStallReport { + public static final class ConnectivityDiagnosticsManager.DataStallReport implements android.os.Parcelable { ctor public ConnectivityDiagnosticsManager.DataStallReport(@NonNull android.net.Network, long, int, @NonNull android.os.PersistableBundle); - field public final int detectionMethod; - field @NonNull public final android.net.Network network; - field public final long reportTimestamp; - field @NonNull public final android.os.PersistableBundle stallDetails; + method public int describeContents(); + method public int getDetectionMethod(); + method @NonNull public android.net.Network getNetwork(); + method public long getReportTimestamp(); + method @NonNull public android.os.PersistableBundle getStallDetails(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.ConnectivityDiagnosticsManager.DataStallReport> CREATOR; + field public static final int DETECTION_METHOD_DNS_EVENTS = 1; // 0x1 + field public static final int DETECTION_METHOD_TCP_METRICS = 2; // 0x2 } public class ConnectivityManager { @@ -29937,6 +30013,7 @@ package android.net { } @Deprecated public class NetworkInfo implements android.os.Parcelable { + ctor @Deprecated public NetworkInfo(int, int, @Nullable String, @Nullable String); method @Deprecated public int describeContents(); method @Deprecated @NonNull public android.net.NetworkInfo.DetailedState getDetailedState(); method @Deprecated public String getExtraInfo(); @@ -29951,6 +30028,7 @@ package android.net { method @Deprecated public boolean isConnectedOrConnecting(); method @Deprecated public boolean isFailover(); method @Deprecated public boolean isRoaming(); + method @Deprecated public void setDetailedState(@NonNull android.net.NetworkInfo.DetailedState, @Nullable String, @Nullable String); method @Deprecated public void writeToParcel(android.os.Parcel, int); field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkInfo> CREATOR; } @@ -43470,7 +43548,7 @@ package android.service.voice { field public static final int STATE_HARDWARE_UNAVAILABLE = -2; // 0xfffffffe field public static final int STATE_KEYPHRASE_ENROLLED = 2; // 0x2 field public static final int STATE_KEYPHRASE_UNENROLLED = 1; // 0x1 - field public static final int STATE_KEYPHRASE_UNSUPPORTED = -1; // 0xffffffff + field @Deprecated public static final int STATE_KEYPHRASE_UNSUPPORTED = -1; // 0xffffffff } public abstract static class AlwaysOnHotwordDetector.Callback { @@ -43689,6 +43767,7 @@ package android.service.wallpaper { method public void onSurfaceRedrawNeeded(android.view.SurfaceHolder); method public void onTouchEvent(android.view.MotionEvent); method public void onVisibilityChanged(boolean); + method public void onZoomChanged(@FloatRange(from=0.0f, to=1.0f) float); method public void setOffsetNotificationsEnabled(boolean); method public void setTouchEventsEnabled(boolean); } @@ -44832,6 +44911,7 @@ package android.telecom { field public static final int PROPERTY_GENERIC_CONFERENCE = 2; // 0x2 field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 128; // 0x80 field public static final int PROPERTY_HIGH_DEF_AUDIO = 16; // 0x10 + field public static final int PROPERTY_IS_ADHOC_CONFERENCE = 8192; // 0x2000 field public static final int PROPERTY_IS_EXTERNAL_CALL = 64; // 0x40 field public static final int PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL = 2048; // 0x800 field public static final int PROPERTY_RTT = 1024; // 0x400 @@ -44909,6 +44989,7 @@ package android.telecom { public abstract class Conference extends android.telecom.Conferenceable { ctor public Conference(android.telecom.PhoneAccountHandle); method public final boolean addConnection(android.telecom.Connection); + method @NonNull public static android.telecom.Conference createFailedConference(@NonNull android.telecom.DisconnectCause, @NonNull android.telecom.PhoneAccountHandle); method public final void destroy(); method public final android.telecom.CallAudioState getCallAudioState(); method public final java.util.List<android.telecom.Connection> getConferenceableConnections(); @@ -44923,6 +45004,8 @@ package android.telecom { method public final android.telecom.StatusHints getStatusHints(); method public android.telecom.Connection.VideoProvider getVideoProvider(); method public int getVideoState(); + method public final boolean isRingbackRequested(); + method public void onAnswer(int); method public void onCallAudioStateChanged(android.telecom.CallAudioState); method public void onConnectionAdded(android.telecom.Connection); method public void onDisconnect(); @@ -44931,6 +45014,7 @@ package android.telecom { method public void onMerge(android.telecom.Connection); method public void onMerge(); method public void onPlayDtmfTone(char); + method public void onReject(); method public void onSeparate(android.telecom.Connection); method public void onStopDtmfTone(); method public void onSwap(); @@ -44950,6 +45034,8 @@ package android.telecom { method public final void setDisconnected(android.telecom.DisconnectCause); method public final void setExtras(@Nullable android.os.Bundle); method public final void setOnHold(); + method public final void setRingbackRequested(boolean); + method public final void setRinging(); method public final void setStatusHints(android.telecom.StatusHints); method public final void setVideoProvider(android.telecom.Connection, android.telecom.Connection.VideoProvider); method public final void setVideoState(android.telecom.Connection, int); @@ -45107,6 +45193,7 @@ package android.telecom { field public static final int PROPERTY_ASSISTED_DIALING_USED = 512; // 0x200 field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 32; // 0x20 field public static final int PROPERTY_HIGH_DEF_AUDIO = 4; // 0x4 + field public static final int PROPERTY_IS_ADHOC_CONFERENCE = 4096; // 0x1000 field public static final int PROPERTY_IS_EXTERNAL_CALL = 16; // 0x10 field public static final int PROPERTY_IS_RTT = 256; // 0x100 field public static final int PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL = 1024; // 0x400 @@ -45179,8 +45266,10 @@ package android.telecom { method public android.telecom.PhoneAccountHandle getAccountHandle(); method public android.net.Uri getAddress(); method public android.os.Bundle getExtras(); + method @Nullable public java.util.List<android.net.Uri> getParticipants(); method public android.telecom.Connection.RttTextStream getRttTextStream(); method public int getVideoState(); + method public boolean isAdhocConferenceCall(); method public boolean isRequestingRtt(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telecom.ConnectionRequest> CREATOR; @@ -45200,9 +45289,13 @@ package android.telecom { method public void onConference(android.telecom.Connection, android.telecom.Connection); method public void onConnectionServiceFocusGained(); method public void onConnectionServiceFocusLost(); + method @Nullable public android.telecom.Conference onCreateIncomingConference(@Nullable android.telecom.PhoneAccountHandle, @Nullable android.telecom.ConnectionRequest); + method public void onCreateIncomingConferenceFailed(@Nullable android.telecom.PhoneAccountHandle, @Nullable android.telecom.ConnectionRequest); method public android.telecom.Connection onCreateIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); method public void onCreateIncomingConnectionFailed(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); method public android.telecom.Connection onCreateIncomingHandoverConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); + method @Nullable public android.telecom.Conference onCreateOutgoingConference(@Nullable android.telecom.PhoneAccountHandle, @Nullable android.telecom.ConnectionRequest); + method public void onCreateOutgoingConferenceFailed(@Nullable android.telecom.PhoneAccountHandle, @Nullable android.telecom.ConnectionRequest); method public android.telecom.Connection onCreateOutgoingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); method public void onCreateOutgoingConnectionFailed(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); method public android.telecom.Connection onCreateOutgoingHandoverConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); @@ -45317,6 +45410,7 @@ package android.telecom { method public boolean supportsUriScheme(String); method public android.telecom.PhoneAccount.Builder toBuilder(); method public void writeToParcel(android.os.Parcel, int); + field public static final int CAPABILITY_ADHOC_CONFERENCE_CALLING = 16384; // 0x4000 field public static final int CAPABILITY_CALL_PROVIDER = 2; // 0x2 field public static final int CAPABILITY_CALL_SUBJECT = 64; // 0x40 field public static final int CAPABILITY_CONNECTION_MANAGER = 1; // 0x1 @@ -45512,6 +45606,7 @@ package android.telecom { method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ANSWER_PHONE_CALLS, android.Manifest.permission.MODIFY_PHONE_STATE}) public void acceptRingingCall(); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ANSWER_PHONE_CALLS, android.Manifest.permission.MODIFY_PHONE_STATE}) public void acceptRingingCall(int); method public void addNewIncomingCall(android.telecom.PhoneAccountHandle, android.os.Bundle); + method public void addNewIncomingConference(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.os.Bundle); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void cancelMissedCallsNotification(); method public android.content.Intent createManageBlockedNumbersIntent(); method @Deprecated @RequiresPermission(android.Manifest.permission.ANSWER_PHONE_CALLS) public boolean endCall(); @@ -45539,6 +45634,7 @@ package android.telecom { method public void registerPhoneAccount(android.telecom.PhoneAccount); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void showInCallScreen(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void silenceRinger(); + method @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void startConference(@NonNull java.util.List<android.net.Uri>, @NonNull android.os.Bundle); method public void unregisterPhoneAccount(android.telecom.PhoneAccountHandle); field public static final String ACTION_CHANGE_DEFAULT_DIALER = "android.telecom.action.CHANGE_DEFAULT_DIALER"; field public static final String ACTION_CHANGE_PHONE_ACCOUNTS = "android.telecom.action.CHANGE_PHONE_ACCOUNTS"; @@ -45859,6 +45955,7 @@ package android.telephony { field public static final String KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY = "5g_nr_ssrsrp_thresholds_int_array"; field public static final String KEY_5G_NR_SSRSRQ_THRESHOLDS_INT_ARRAY = "5g_nr_ssrsrq_thresholds_int_array"; field public static final String KEY_5G_NR_SSSINR_THRESHOLDS_INT_ARRAY = "5g_nr_sssinr_thresholds_int_array"; + field public static final String KEY_5G_WATCHDOG_TIME_MS_LONG = "5g_watchdog_time_long"; field public static final String KEY_ADDITIONAL_CALL_SETTING_BOOL = "additional_call_setting_bool"; field public static final String KEY_ALLOW_ADDING_APNS_BOOL = "allow_adding_apns_bool"; field public static final String KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL = "allow_add_call_during_video_call"; @@ -45942,6 +46039,7 @@ package android.telephony { field public static final String KEY_DATA_LIMIT_NOTIFICATION_BOOL = "data_limit_notification_bool"; field public static final String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long"; field public static final String KEY_DATA_RAPID_NOTIFICATION_BOOL = "data_rapid_notification_bool"; + field public static final String KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG = "data_switch_validation_timeout_long"; field public static final String KEY_DATA_WARNING_NOTIFICATION_BOOL = "data_warning_notification_bool"; field public static final String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = "data_warning_threshold_bytes_long"; field public static final String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string"; @@ -46024,6 +46122,8 @@ package android.telephony { field public static final String KEY_ONLY_AUTO_SELECT_IN_HOME_NETWORK_BOOL = "only_auto_select_in_home_network"; field public static final String KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY = "only_single_dc_allowed_int_array"; field public static final String KEY_OPERATOR_SELECTION_EXPAND_BOOL = "operator_selection_expand_bool"; + field public static final String KEY_OPPORTUNISTIC_NETWORK_BACKOFF_TIME_LONG = "opportunistic_network_backoff_time_long"; + field public static final String KEY_OPPORTUNISTIC_NETWORK_DATA_SWITCH_EXIT_HYSTERESIS_TIME_LONG = "opportunistic_network_data_switch_exit_hysteresis_time_long"; field public static final String KEY_OPPORTUNISTIC_NETWORK_DATA_SWITCH_HYSTERESIS_TIME_LONG = "opportunistic_network_data_switch_hysteresis_time_long"; field public static final String KEY_OPPORTUNISTIC_NETWORK_ENTRY_OR_EXIT_HYSTERESIS_TIME_LONG = "opportunistic_network_entry_or_exit_hysteresis_time_long"; field public static final String KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_BANDWIDTH_INT = "opportunistic_network_entry_threshold_bandwidth_int"; @@ -46031,6 +46131,9 @@ package android.telephony { field public static final String KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_RSSNR_INT = "opportunistic_network_entry_threshold_rssnr_int"; field public static final String KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSRP_INT = "opportunistic_network_exit_threshold_rsrp_int"; field public static final String KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSSNR_INT = "opportunistic_network_exit_threshold_rssnr_int"; + field public static final String KEY_OPPORTUNISTIC_NETWORK_MAX_BACKOFF_TIME_LONG = "opportunistic_network_max_backoff_time_long"; + field public static final String KEY_OPPORTUNISTIC_NETWORK_PING_PONG_TIME_LONG = "opportunistic_network_ping_pong_time_long"; + field public static final String KEY_PING_TEST_BEFORE_DATA_SWITCH_BOOL = "ping_test_before_data_switch_bool"; field public static final String KEY_PREFER_2G_BOOL = "prefer_2g_bool"; field public static final String KEY_PREVENT_CLIR_ACTIVATION_AND_DEACTIVATION_CODE_BOOL = "prevent_clir_activation_and_deactivation_code_bool"; field public static final String KEY_RADIO_RESTART_FAILURE_CAUSES_INT_ARRAY = "radio_restart_failure_causes_int_array"; @@ -46047,6 +46150,7 @@ package android.telephony { field public static final String KEY_SHOW_CALL_BLOCKING_DISABLED_NOTIFICATION_ALWAYS_BOOL = "show_call_blocking_disabled_notification_always_bool"; field public static final String KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING = "show_carrier_data_icon_pattern_string"; field public static final String KEY_SHOW_CDMA_CHOICES_BOOL = "show_cdma_choices_bool"; + field public static final String KEY_SHOW_FORWARDED_NUMBER_BOOL = "show_forwarded_number_bool"; field public static final String KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL = "show_iccid_in_sim_status_bool"; field public static final String KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL = "show_ims_registration_status_bool"; field public static final String KEY_SHOW_ONSCREEN_DIAL_BUTTON_BOOL = "show_onscreen_dial_button_bool"; @@ -46057,6 +46161,7 @@ package android.telephony { field public static final String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool"; field public static final String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool"; field public static final String KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL = "support_3gpp_call_forwarding_while_roaming_bool"; + field public static final String KEY_SUPPORT_ADHOC_CONFERENCE_CALLS_BOOL = "support_adhoc_conference_calls_bool"; field public static final String KEY_SUPPORT_CLIR_NETWORK_DEFAULT_BOOL = "support_clir_network_default_bool"; field public static final String KEY_SUPPORT_CONFERENCE_CALL_BOOL = "support_conference_call_bool"; field public static final String KEY_SUPPORT_EMERGENCY_SMS_OVER_IMS_BOOL = "support_emergency_sms_over_ims_bool"; @@ -46066,6 +46171,7 @@ package android.telephony { field public static final String KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL = "support_swap_after_merge_bool"; field public static final String KEY_SUPPORT_TDSCDMA_BOOL = "support_tdscdma_bool"; field public static final String KEY_SUPPORT_TDSCDMA_ROAMING_NETWORKS_STRING_ARRAY = "support_tdscdma_roaming_networks_string_array"; + field public static final String KEY_SWITCH_DATA_TO_PRIMARY_IF_PRIMARY_IS_OOS_BOOL = "switch_data_to_primary_if_primary_is_oos_bool"; field public static final String KEY_TREAT_DOWNGRADED_VIDEO_CALLS_AS_VIDEO_CALLS_BOOL = "treat_downgraded_video_calls_as_video_calls_bool"; field public static final String KEY_TTY_SUPPORTED_BOOL = "tty_supported_bool"; field public static final String KEY_UNLOGGABLE_NUMBERS_STRING_ARRAY = "unloggable_numbers_string_array"; @@ -46091,6 +46197,15 @@ package android.telephony { field public static final String KEY_WORLD_PHONE_BOOL = "world_phone_bool"; } + public static final class CarrierConfigManager.Apn { + field public static final String KEY_PREFIX = "apn."; + field public static final String KEY_SETTINGS_DEFAULT_PROTOCOL_STRING = "apn.settings_default_protocol_string"; + field public static final String KEY_SETTINGS_DEFAULT_ROAMING_PROTOCOL_STRING = "apn.settings_default_roaming_protocol_string"; + field public static final String PROTOCOL_IPV4 = "IP"; + field public static final String PROTOCOL_IPV4V6 = "IPV4V6"; + field public static final String PROTOCOL_IPV6 = "IPV6"; + } + public static final class CarrierConfigManager.Gps { field public static final String KEY_PERSIST_LPP_MODE_BOOL = "gps.persist_lpp_mode_bool"; field public static final String KEY_PREFIX = "gps."; @@ -46173,6 +46288,7 @@ package android.telephony { } public final class CellIdentityGsm extends android.telephony.CellIdentity { + method @NonNull public java.util.List<java.lang.String> getAdditionalPlmns(); method public int getArfcn(); method public int getBsic(); method public int getCid(); @@ -46188,8 +46304,11 @@ package android.telephony { } public final class CellIdentityLte extends android.telephony.CellIdentity { + method @NonNull public java.util.List<java.lang.String> getAdditionalPlmns(); + method @NonNull public java.util.List<java.lang.Integer> getBands(); method public int getBandwidth(); method public int getCi(); + method @Nullable public android.telephony.ClosedSubscriberGroupInfo getClosedSubscriberGroupInfo(); method public int getEarfcn(); method @Deprecated public int getMcc(); method @Nullable public String getMccString(); @@ -46203,6 +46322,8 @@ package android.telephony { } public final class CellIdentityNr extends android.telephony.CellIdentity { + method @NonNull public java.util.List<java.lang.String> getAdditionalPlmns(); + method @NonNull public java.util.List<java.lang.Integer> getBands(); method @Nullable public String getMccString(); method @Nullable public String getMncString(); method public long getNci(); @@ -46214,7 +46335,9 @@ package android.telephony { } public final class CellIdentityTdscdma extends android.telephony.CellIdentity { + method @NonNull public java.util.List<java.lang.String> getAdditionalPlmns(); method public int getCid(); + method @Nullable public android.telephony.ClosedSubscriberGroupInfo getClosedSubscriberGroupInfo(); method public int getCpid(); method public int getLac(); method @Nullable public String getMccString(); @@ -46226,7 +46349,9 @@ package android.telephony { } public final class CellIdentityWcdma extends android.telephony.CellIdentity { + method @NonNull public java.util.List<java.lang.String> getAdditionalPlmns(); method public int getCid(); + method @Nullable public android.telephony.ClosedSubscriberGroupInfo getClosedSubscriberGroupInfo(); method public int getLac(); method @Deprecated public int getMcc(); method @Nullable public String getMccString(); @@ -46396,6 +46521,15 @@ package android.telephony { field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthWcdma> CREATOR; } + public final class ClosedSubscriberGroupInfo implements android.os.Parcelable { + method public int describeContents(); + method @IntRange(from=0, to=134217727) public int getCsgIdentity(); + method public boolean getCsgIndicator(); + method @NonNull public String getHomeNodebName(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ClosedSubscriberGroupInfo> CREATOR; + } + public class IccOpenLogicalChannelResponse implements android.os.Parcelable { method public int describeContents(); method public int getChannel(); @@ -47110,6 +47244,7 @@ package android.telephony { method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getImei(); method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getImei(int); method @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_SMS, android.Manifest.permission.READ_PHONE_NUMBERS}) public String getLine1Number(); + method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public String getManualNetworkSelectionPlmn(); method @Nullable public String getManufacturerCode(); method @Nullable public String getManufacturerCode(int); method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getMeid(); @@ -47165,6 +47300,7 @@ package android.telephony { method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataRoamingEnabled(); method public boolean isEmergencyNumber(@NonNull String); method public boolean isHearingAidCompatibilitySupported(); + method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public boolean isManualNetworkSelectionAllowed(); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int isMultiSimSupported(); method public boolean isNetworkRoaming(); method public boolean isRttSupported(); @@ -47573,10 +47709,42 @@ package android.telephony.euicc { field public static final int EMBEDDED_SUBSCRIPTION_RESULT_ERROR = 2; // 0x2 field public static final int EMBEDDED_SUBSCRIPTION_RESULT_OK = 0; // 0x0 field public static final int EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR = 1; // 0x1 + field public static final int ERROR_ADDRESS_MISSING = 10011; // 0x271b + field public static final int ERROR_CARRIER_LOCKED = 10000; // 0x2710 + field public static final int ERROR_CERTIFICATE_ERROR = 10012; // 0x271c + field public static final int ERROR_CONNECTION_ERROR = 10014; // 0x271e + field public static final int ERROR_DISALLOWED_BY_PPR = 10010; // 0x271a + field public static final int ERROR_EUICC_GSMA_INSTALL_ERROR = 10009; // 0x2719 + field public static final int ERROR_EUICC_INSUFFICIENT_MEMORY = 10004; // 0x2714 + field public static final int ERROR_EUICC_MISSING = 10006; // 0x2716 + field public static final int ERROR_INCOMPATIBLE_CARRIER = 10003; // 0x2713 + field public static final int ERROR_INVALID_ACTIVATION_CODE = 10001; // 0x2711 + field public static final int ERROR_INVALID_CONFIRMATION_CODE = 10002; // 0x2712 + field public static final int ERROR_INVALID_RESPONSE = 10015; // 0x271f + field public static final int ERROR_NO_PROFILES_AVAILABLE = 10013; // 0x271d + field public static final int ERROR_OPERATION_BUSY = 10016; // 0x2720 + field public static final int ERROR_SIM_MISSING = 10008; // 0x2718 + field public static final int ERROR_TIME_OUT = 10005; // 0x2715 + field public static final int ERROR_UNSUPPORTED_VERSION = 10007; // 0x2717 field public static final String EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_DETAILED_CODE"; field public static final String EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION"; + field public static final String EXTRA_EMBEDDED_SUBSCRIPTION_ERROR_CODE = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_ERROR_CODE"; + field public static final String EXTRA_EMBEDDED_SUBSCRIPTION_OPERATION_CODE = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_OPERATION_CODE"; + field public static final String EXTRA_EMBEDDED_SUBSCRIPTION_SMDX_REASON_CODE = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_SMDX_REASON_CODE"; + field public static final String EXTRA_EMBEDDED_SUBSCRIPTION_SMDX_SUBJECT_CODE = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_SMDX_SUBJECT_CODE"; field public static final String EXTRA_USE_QR_SCANNER = "android.telephony.euicc.extra.USE_QR_SCANNER"; field public static final String META_DATA_CARRIER_ICON = "android.telephony.euicc.carriericon"; + field public static final int OPERATION_APDU = 8; // 0x8 + field public static final int OPERATION_DOWNLOAD = 5; // 0x5 + field public static final int OPERATION_EUICC_CARD = 3; // 0x3 + field public static final int OPERATION_EUICC_GSMA = 7; // 0x7 + field public static final int OPERATION_HTTP = 11; // 0xb + field public static final int OPERATION_METADATA = 6; // 0x6 + field public static final int OPERATION_SIM_SLOT = 2; // 0x2 + field public static final int OPERATION_SMDX = 9; // 0x9 + field public static final int OPERATION_SMDX_SUBJECT_REASON_CODE = 10; // 0xa + field public static final int OPERATION_SWITCH = 4; // 0x4 + field public static final int OPERATION_SYSTEM = 1; // 0x1 } } @@ -52499,6 +52667,20 @@ package android.view { field @NonNull public static final android.os.Parcelable.Creator<android.view.SurfaceControl.Transaction> CREATOR; } + public class SurfaceControlViewHost { + ctor public SurfaceControlViewHost(@NonNull android.content.Context, @NonNull android.view.Display, @Nullable android.os.IBinder); + method public void addView(@NonNull android.view.View, int, int); + method @Nullable public android.view.SurfaceControlViewHost.SurfacePackage getSurfacePackage(); + method public void relayout(int, int); + method public void release(); + } + + public static final class SurfaceControlViewHost.SurfacePackage implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.view.SurfaceControlViewHost.SurfacePackage> CREATOR; + } + public interface SurfaceHolder { method public void addCallback(android.view.SurfaceHolder.Callback); method public android.view.Surface getSurface(); @@ -52543,7 +52725,9 @@ package android.view { ctor public SurfaceView(android.content.Context, android.util.AttributeSet, int, int); method public boolean gatherTransparentRegion(android.graphics.Region); method public android.view.SurfaceHolder getHolder(); + method @Nullable public android.os.IBinder getHostToken(); method public android.view.SurfaceControl getSurfaceControl(); + method public void setChildSurfacePackage(@NonNull android.view.SurfaceControlViewHost.SurfacePackage); method public void setSecure(boolean); method public void setZOrderMediaOverlay(boolean); method public void setZOrderOnTop(boolean); @@ -54142,6 +54326,7 @@ package android.view { method public final void removeOnFrameMetricsAvailableListener(android.view.Window.OnFrameMetricsAvailableListener); method public boolean requestFeature(int); method @NonNull public final <T extends android.view.View> T requireViewById(@IdRes int); + method public void resetOnContentApplyWindowInsetsListener(); method public abstract void restoreHierarchyState(android.os.Bundle); method public abstract android.os.Bundle saveHierarchyState(); method public void setAllowEnterTransitionOverlap(boolean); @@ -54180,6 +54365,7 @@ package android.view { method public abstract void setNavigationBarColor(@ColorInt int); method public void setNavigationBarContrastEnforced(boolean); method public void setNavigationBarDividerColor(@ColorInt int); + method public void setOnContentApplyWindowInsetsListener(@Nullable android.view.Window.OnContentApplyWindowInsetsListener); method public void setPreferMinimalPostProcessing(boolean); method public void setReenterTransition(android.transition.Transition); method public abstract void setResizingCaptionDrawable(android.graphics.drawable.Drawable); @@ -54274,6 +54460,10 @@ package android.view { method @Nullable public android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int); } + public static interface Window.OnContentApplyWindowInsetsListener { + method @NonNull public android.util.Pair<android.graphics.Insets,android.view.WindowInsets> onContentApplyWindowInsets(@NonNull android.view.WindowInsets); + } + public static interface Window.OnFrameMetricsAvailableListener { method public void onFrameMetricsAvailable(android.view.Window, android.view.FrameMetrics, int); } @@ -54397,7 +54587,7 @@ package android.view { method public float getInterpolatedFraction(); method @Nullable public android.view.animation.Interpolator getInterpolator(); method public int getTypeMask(); - method public void setDuration(long); + method public void setAlpha(@FloatRange(from=0.0f, to=1.0f) float); method public void setFraction(@FloatRange(from=0.0f, to=1.0f) float); } @@ -54418,7 +54608,7 @@ package android.view { } public interface WindowInsetsController { - method public default void controlInputMethodAnimation(long, @NonNull android.view.WindowInsetsAnimationControlListener); + method public default void controlInputMethodAnimation(long, @Nullable android.view.animation.Interpolator, @NonNull android.view.WindowInsetsAnimationControlListener); method public int getSystemBarsAppearance(); method public int getSystemBarsBehavior(); method public default void hideInputMethod(); @@ -54430,7 +54620,7 @@ package android.view { } public interface WindowManager extends android.view.ViewManager { - method public android.view.Display getDefaultDisplay(); + method @Deprecated public android.view.Display getDefaultDisplay(); method public void removeViewImmediate(android.view.View); } @@ -55690,7 +55880,12 @@ package android.view.inputmethod { ctor public EditorInfo(); method public int describeContents(); method public void dump(android.util.Printer, String); + method @Nullable public CharSequence getInitialSelectedText(int); + method @Nullable public CharSequence getInitialTextAfterCursor(int, int); + method @Nullable public CharSequence getInitialTextBeforeCursor(int, int); method public final void makeCompatible(int); + method public void setInitialSurroundingSubText(@NonNull CharSequence, int); + method public void setInitialSurroundingText(@NonNull CharSequence); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.EditorInfo> CREATOR; field public static final int IME_ACTION_DONE = 6; // 0x6 diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt index 1cb1c20c738c..6f4a27ecfb0b 100644 --- a/api/module-lib-current.txt +++ b/api/module-lib-current.txt @@ -118,10 +118,32 @@ package android.timezone { } public final class TimeZoneFinder { + method @Nullable public String getIanaVersion(); method @NonNull public static android.timezone.TimeZoneFinder getInstance(); method @Nullable public android.timezone.CountryTimeZones lookupCountryTimeZones(@NonNull String); } + public final class TzDataSetVersion { + method public static int currentFormatMajorVersion(); + method public static int currentFormatMinorVersion(); + method public int getFormatMajorVersion(); + method public int getFormatMinorVersion(); + method public int getRevision(); + method @NonNull public String getRulesVersion(); + method public static boolean isCompatibleWithThisDevice(android.timezone.TzDataSetVersion); + method @NonNull public static android.timezone.TzDataSetVersion read() throws java.io.IOException, android.timezone.TzDataSetVersion.TzDataSetException; + } + + public static class TzDataSetVersion.TzDataSetException extends java.lang.Exception { + ctor public TzDataSetVersion.TzDataSetException(String); + ctor public TzDataSetVersion.TzDataSetException(String, Throwable); + } + + public final class ZoneInfoDb { + method @NonNull public static android.timezone.ZoneInfoDb getInstance(); + method @NonNull public String getVersion(); + } + } package android.util { diff --git a/api/system-current.txt b/api/system-current.txt index 4bd2ed574e96..fa62d4419f8d 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -55,6 +55,7 @@ package android { field public static final String CAPTURE_AUDIO_HOTWORD = "android.permission.CAPTURE_AUDIO_HOTWORD"; field public static final String CAPTURE_MEDIA_OUTPUT = "android.permission.CAPTURE_MEDIA_OUTPUT"; field public static final String CAPTURE_TV_INPUT = "android.permission.CAPTURE_TV_INPUT"; + field public static final String CAPTURE_VOICE_COMMUNICATION_OUTPUT = "android.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT"; field public static final String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE"; field public static final String CHANGE_DEVICE_IDLE_TEMP_WHITELIST = "android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"; field public static final String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA"; @@ -129,6 +130,7 @@ package android { field public static final String MODIFY_PARENTAL_CONTROLS = "android.permission.MODIFY_PARENTAL_CONTROLS"; field public static final String MODIFY_QUIET_MODE = "android.permission.MODIFY_QUIET_MODE"; field public static final String MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE = "android.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE"; + field public static final String MONITOR_DEVICE_CONFIG_ACCESS = "android.permission.MONITOR_DEVICE_CONFIG_ACCESS"; field public static final String MOVE_PACKAGE = "android.permission.MOVE_PACKAGE"; field public static final String NETWORK_AIRPLANE_MODE = "android.permission.NETWORK_AIRPLANE_MODE"; field public static final String NETWORK_CARRIER_PROVISIONING = "android.permission.NETWORK_CARRIER_PROVISIONING"; @@ -186,6 +188,7 @@ package android { field public static final String REMOTE_DISPLAY_PROVIDER = "android.permission.REMOTE_DISPLAY_PROVIDER"; field public static final String REMOVE_DRM_CERTIFICATES = "android.permission.REMOVE_DRM_CERTIFICATES"; field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS"; + field public static final String REQUEST_NETWORK_SCORES = "android.permission.REQUEST_NETWORK_SCORES"; field public static final String REQUEST_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE"; field public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD"; field public static final String RESTORE_RUNTIME_PERMISSIONS = "android.permission.RESTORE_RUNTIME_PERMISSIONS"; @@ -231,7 +234,7 @@ package android { field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG"; field public static final String WRITE_DREAM_STATE = "android.permission.WRITE_DREAM_STATE"; field public static final String WRITE_EMBEDDED_SUBSCRIPTIONS = "android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"; - field public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE"; + field @Deprecated public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE"; field public static final String WRITE_OBB = "android.permission.WRITE_OBB"; } @@ -388,6 +391,7 @@ package android.app { field public static final String OPSTR_POST_NOTIFICATION = "android:post_notification"; field public static final String OPSTR_PROJECT_MEDIA = "android:project_media"; field public static final String OPSTR_READ_CLIPBOARD = "android:read_clipboard"; + field public static final String OPSTR_READ_DEVICE_IDENTIFIERS = "android:read_device_identifiers"; field public static final String OPSTR_READ_ICC_SMS = "android:read_icc_sms"; field public static final String OPSTR_READ_MEDIA_AUDIO = "android:read_media_audio"; field public static final String OPSTR_READ_MEDIA_IMAGES = "android:read_media_images"; @@ -1174,6 +1178,7 @@ package android.app.contentsuggestions { method public void classifyContentSelections(@NonNull android.app.contentsuggestions.ClassificationsRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.contentsuggestions.ContentSuggestionsManager.ClassificationsCallback); method public boolean isEnabled(); method public void notifyInteraction(@NonNull String, @NonNull android.os.Bundle); + method public void provideContextImage(@NonNull android.graphics.Bitmap, @NonNull android.os.Bundle); method public void provideContextImage(int, @NonNull android.os.Bundle); method public void suggestContentSelections(@NonNull android.app.contentsuggestions.SelectionsRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.contentsuggestions.ContentSuggestionsManager.SelectionsCallback); } @@ -1605,12 +1610,13 @@ package android.bluetooth { field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED"; } - public final class BluetoothPan implements android.bluetooth.BluetoothProfile { - method protected void finalize(); + public final class BluetoothPan implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile { + method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public void close(); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH) protected void finalize(); method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(); - method public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice); - method public boolean isTetheringOn(); - method public void setBluetoothTethering(boolean); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isTetheringOn(); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public void setBluetoothTethering(boolean); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int); field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED"; field public static final String EXTRA_LOCAL_ROLE = "android.bluetooth.pan.extra.LOCAL_ROLE"; @@ -1718,6 +1724,13 @@ package android.companion { package android.content { + public class ApexContext { + method @NonNull public static android.content.ApexContext getApexContext(@NonNull String); + method @NonNull public java.io.File getCredentialProtectedDataDirForUser(@NonNull android.os.UserHandle); + method @NonNull public java.io.File getDeviceProtectedDataDir(); + method @NonNull public java.io.File getDeviceProtectedDataDirForUser(@NonNull android.os.UserHandle); + } + public abstract class BroadcastReceiver { method @NonNull public final android.os.UserHandle getSendingUser(); } @@ -2291,6 +2304,7 @@ package android.content.pm { field public static final int MATCH_ANY_USER = 4194304; // 0x400000 field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000 field public static final int MATCH_INSTANT = 8388608; // 0x800000 + field public static final int MODULE_APEX_NAME = 1; // 0x1 field public static final int RESTRICTION_HIDE_FROM_SUGGESTIONS = 1; // 0x1 field public static final int RESTRICTION_HIDE_NOTIFICATIONS = 2; // 0x2 field public static final int RESTRICTION_NONE = 0; // 0x0 @@ -3624,9 +3638,34 @@ package android.hardware.radio { package android.hardware.soundtrigger { public class SoundTrigger { + field public static final int RECOGNITION_MODE_GENERIC = 8; // 0x8 + field public static final int RECOGNITION_MODE_USER_AUTHENTICATION = 4; // 0x4 + field public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 2; // 0x2 + field public static final int RECOGNITION_MODE_VOICE_TRIGGER = 1; // 0x1 field public static final int STATUS_OK = 0; // 0x0 } + public static final class SoundTrigger.Keyphrase implements android.os.Parcelable { + ctor public SoundTrigger.Keyphrase(int, int, @NonNull java.util.Locale, @NonNull String, @Nullable int[]); + method @NonNull public static android.hardware.soundtrigger.SoundTrigger.Keyphrase readFromParcel(@NonNull android.os.Parcel); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.Keyphrase> CREATOR; + field public final int id; + field @NonNull public final java.util.Locale locale; + field public final int recognitionModes; + field @NonNull public final String text; + field @NonNull public final int[] users; + } + + public static final class SoundTrigger.KeyphraseSoundModel extends android.hardware.soundtrigger.SoundTrigger.SoundModel implements android.os.Parcelable { + ctor public SoundTrigger.KeyphraseSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], @Nullable android.hardware.soundtrigger.SoundTrigger.Keyphrase[], int); + ctor public SoundTrigger.KeyphraseSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], @Nullable android.hardware.soundtrigger.SoundTrigger.Keyphrase[]); + method @NonNull public static android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel readFromParcel(@NonNull android.os.Parcel); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel> CREATOR; + field @NonNull public final android.hardware.soundtrigger.SoundTrigger.Keyphrase[] keyphrases; + } + public static final class SoundTrigger.ModelParamRange implements android.os.Parcelable { method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.ModelParamRange> CREATOR; @@ -3665,6 +3704,16 @@ package android.hardware.soundtrigger { method public boolean isCaptureAvailable(); } + public static class SoundTrigger.SoundModel { + field public static final int TYPE_GENERIC_SOUND = 1; // 0x1 + field public static final int TYPE_KEYPHRASE = 0; // 0x0 + field @NonNull public final byte[] data; + field public final int type; + field @NonNull public final java.util.UUID uuid; + field @NonNull public final java.util.UUID vendorUuid; + field public final int version; + } + } package android.hardware.usb { @@ -3677,6 +3726,7 @@ package android.hardware.usb { method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public long getCurrentFunctions(); method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_USB) public java.util.List<android.hardware.usb.UsbPort> getPorts(); method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void grantPermission(android.hardware.usb.UsbDevice, String); + method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void resetUsbGadget(); method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void setCurrentFunctions(long); field @RequiresPermission(android.Manifest.permission.MANAGE_USB) public static final String ACTION_USB_PORT_CHANGED = "android.hardware.usb.action.USB_PORT_CHANGED"; field public static final String ACTION_USB_STATE = "android.hardware.usb.action.USB_STATE"; @@ -4173,9 +4223,11 @@ package android.media { method @Deprecated public int abandonAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes); method public void clearAudioServerStateCallback(); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int dispatchAudioFocusChange(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy); + method @IntRange(from=0) public int getAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioProductStrategy> getAudioProductStrategies(); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioVolumeGroup> getAudioVolumeGroups(); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAddress> getDevicesForAttributes(@NonNull android.media.AudioAttributes); + method @IntRange(from=0) public int getMaxAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo); method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMaxVolumeIndexForAttributes(@NonNull android.media.AudioAttributes); method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMinVolumeIndexForAttributes(@NonNull android.media.AudioAttributes); method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioDeviceAddress getPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy); @@ -4189,6 +4241,7 @@ package android.media { method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, @NonNull android.media.AudioAttributes, int, int) throws java.lang.IllegalArgumentException; method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.MODIFY_AUDIO_ROUTING}) public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, @NonNull android.media.AudioAttributes, int, int, android.media.audiopolicy.AudioPolicy) throws java.lang.IllegalArgumentException; method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int requestAudioFocus(@NonNull android.media.AudioFocusRequest, @Nullable android.media.audiopolicy.AudioPolicy); + method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo, @IntRange(from=0) int); method public void setAudioServerStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.AudioServerStateCallback); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setFocusRequestResult(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull android.media.AudioDeviceAddress); @@ -4315,6 +4368,7 @@ package android.media.audiopolicy { field public static final int RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET = 2; // 0x2 field public static final int RULE_MATCH_ATTRIBUTE_USAGE = 1; // 0x1 field public static final int RULE_MATCH_UID = 4; // 0x4 + field public static final int RULE_MATCH_USERID = 8; // 0x8 } public static class AudioMixingRule.Builder { @@ -4335,9 +4389,11 @@ package android.media.audiopolicy { method public int getFocusDuckingBehavior(); method public int getStatus(); method public boolean removeUidDeviceAffinity(int); + method public boolean removeUserIdDeviceAffinity(int); method public int setFocusDuckingBehavior(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException; method public void setRegistration(String); method public boolean setUidDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>); + method public boolean setUserIdDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>); method public String toLogFriendlyString(); field public static final int FOCUS_POLICY_DUCKING_DEFAULT = 0; // 0x0 field public static final int FOCUS_POLICY_DUCKING_IN_APP = 0; // 0x0 @@ -4709,6 +4765,7 @@ package android.media.tv.tuner { method public long getSectionFilterLength(); method public int getTsFilterCount(); method public int getVideoFilterCount(); + method public boolean isTimeFilterSupported(); } public class Descrambler implements java.lang.AutoCloseable { @@ -4754,7 +4811,7 @@ package android.media.tv.tuner { public class Tuner implements java.lang.AutoCloseable { ctor @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public Tuner(@NonNull android.content.Context, @NonNull String, int, @Nullable android.media.tv.tuner.Tuner.OnResourceLostListener); method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void clearOnTuneEventListener(); - method public void close(); + method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void close(); method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int connectCiCam(int); method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int disconnectCiCam(); method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int getAvSyncHwId(@NonNull android.media.tv.tuner.filter.Filter); @@ -4772,10 +4829,11 @@ package android.media.tv.tuner { method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int scan(@NonNull android.media.tv.tuner.frontend.FrontendSettings, int, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.frontend.ScanCallback); method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int setLna(boolean); method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void setOnTuneEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.frontend.OnTuneEventListener); - method public void shareFrontendFromTuner(@NonNull android.media.tv.tuner.Tuner); + method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void shareFrontendFromTuner(@NonNull android.media.tv.tuner.Tuner); method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int stopScan(); method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int stopTune(); method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int tune(@NonNull android.media.tv.tuner.frontend.FrontendSettings); + method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void updateResourcePriority(int, int); } public static interface Tuner.OnResourceLostListener { @@ -4999,9 +5057,11 @@ package android.media.tv.tuner.filter { } public class MediaEvent extends android.media.tv.tuner.filter.FilterEvent { + method public long getAudioHandle(); method public long getAvDataId(); method public long getDataLength(); method @Nullable public android.media.tv.tuner.filter.AudioDescriptor getExtraMetaData(); + method @Nullable public android.media.MediaCodec.LinearBlock getLinearBlock(); method public int getMpuSequenceNumber(); method public long getOffset(); method public long getPts(); @@ -5094,7 +5154,16 @@ package android.media.tv.tuner.filter { method public int getVersion(); } - public class SectionSettings extends android.media.tv.tuner.filter.Settings { + public abstract class SectionSettings extends android.media.tv.tuner.filter.Settings { + method public boolean isCrcEnabled(); + method public boolean isRaw(); + method public boolean isRepeat(); + } + + public abstract static class SectionSettings.Builder<T extends android.media.tv.tuner.filter.SectionSettings.Builder<T>> extends android.media.tv.tuner.filter.Settings.Builder<android.media.tv.tuner.filter.SectionSettings.Builder<T>> { + method @NonNull public T setCrcEnabled(boolean); + method @NonNull public T setRaw(boolean); + method @NonNull public T setRepeat(boolean); } public class SectionSettingsWithSectionBits extends android.media.tv.tuner.filter.SectionSettings { @@ -5104,7 +5173,7 @@ package android.media.tv.tuner.filter { method @NonNull public byte[] getMode(); } - public static class SectionSettingsWithSectionBits.Builder extends android.media.tv.tuner.filter.Settings.Builder<android.media.tv.tuner.filter.SectionSettingsWithSectionBits.Builder> { + public static class SectionSettingsWithSectionBits.Builder extends android.media.tv.tuner.filter.SectionSettings.Builder<android.media.tv.tuner.filter.SectionSettingsWithSectionBits.Builder> { method @NonNull public android.media.tv.tuner.filter.SectionSettingsWithSectionBits build(); method @NonNull public android.media.tv.tuner.filter.SectionSettingsWithSectionBits.Builder setFilter(@NonNull byte[]); method @NonNull public android.media.tv.tuner.filter.SectionSettingsWithSectionBits.Builder setMask(@NonNull byte[]); @@ -5117,7 +5186,7 @@ package android.media.tv.tuner.filter { method public int getVersion(); } - public static class SectionSettingsWithTableInfo.Builder extends android.media.tv.tuner.filter.Settings.Builder<android.media.tv.tuner.filter.SectionSettingsWithTableInfo.Builder> { + public static class SectionSettingsWithTableInfo.Builder extends android.media.tv.tuner.filter.SectionSettings.Builder<android.media.tv.tuner.filter.SectionSettingsWithTableInfo.Builder> { method @NonNull public android.media.tv.tuner.filter.SectionSettingsWithTableInfo build(); method @NonNull public android.media.tv.tuner.filter.SectionSettingsWithTableInfo.Builder setTableId(int); method @NonNull public android.media.tv.tuner.filter.SectionSettingsWithTableInfo.Builder setVersion(int); @@ -5155,7 +5224,7 @@ package android.media.tv.tuner.filter { public static class TlvFilterConfiguration.Builder extends android.media.tv.tuner.filter.FilterConfiguration.Builder<android.media.tv.tuner.filter.TlvFilterConfiguration.Builder> { method @NonNull public android.media.tv.tuner.filter.TlvFilterConfiguration build(); - method @NonNull public android.media.tv.tuner.filter.TlvFilterConfiguration.Builder setIsCompressedIpPacket(boolean); + method @NonNull public android.media.tv.tuner.filter.TlvFilterConfiguration.Builder setCompressedIpPacket(boolean); method @NonNull public android.media.tv.tuner.filter.TlvFilterConfiguration.Builder setPacketType(int); method @NonNull public android.media.tv.tuner.filter.TlvFilterConfiguration.Builder setPassthrough(boolean); } @@ -5856,6 +5925,16 @@ package android.media.tv.tuner.frontend { } +package android.media.voice { + + public final class KeyphraseModelManager { + method @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public void deleteKeyphraseSoundModel(int, @NonNull java.util.Locale); + method @Nullable @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel getKeyphraseSoundModel(int, @NonNull java.util.Locale); + method @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public void updateKeyphraseSoundModel(@NonNull android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel); + } + +} + package android.metrics { public class LogMaker { @@ -5912,31 +5991,57 @@ package android.net { field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2 } + public final class CaptivePortalData implements android.os.Parcelable { + method public int describeContents(); + method public long getByteLimit(); + method public long getExpiryTimeMillis(); + method public long getRefreshTimeMillis(); + method @Nullable public android.net.Uri getUserPortalUrl(); + method @Nullable public android.net.Uri getVenueInfoUrl(); + method public boolean isCaptive(); + method public boolean isSessionExtendable(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.CaptivePortalData> CREATOR; + } + + public static class CaptivePortalData.Builder { + ctor public CaptivePortalData.Builder(); + ctor public CaptivePortalData.Builder(@Nullable android.net.CaptivePortalData); + method @NonNull public android.net.CaptivePortalData build(); + method @NonNull public android.net.CaptivePortalData.Builder setBytesRemaining(long); + method @NonNull public android.net.CaptivePortalData.Builder setCaptive(boolean); + method @NonNull public android.net.CaptivePortalData.Builder setExpiryTime(long); + method @NonNull public android.net.CaptivePortalData.Builder setRefreshTime(long); + method @NonNull public android.net.CaptivePortalData.Builder setSessionExtendable(boolean); + method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri); + method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri); + } + public class ConnectivityManager { method @NonNull @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createNattKeepalive(@NonNull android.net.Network, @NonNull android.os.ParcelFileDescriptor, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); method @NonNull @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull java.net.Socket, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); method @Deprecated @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public String getCaptivePortalServerUrl(); - method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void getLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEntitlementResultListener); - method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported(); + method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void getLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEntitlementResultListener); + method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported(); method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public int registerNetworkProvider(@NonNull android.net.NetworkProvider); - method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback); + method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback); method @Deprecated public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, int, int, @NonNull android.os.Handler); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_AIRPLANE_MODE, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public boolean shouldAvoidBadWifi(); method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(@NonNull android.net.Network, @NonNull android.os.Bundle); method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback); method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback, android.os.Handler); - method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void stopTethering(int); + method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void stopTethering(int); method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void unregisterNetworkProvider(@NonNull android.net.NetworkProvider); - method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void unregisterTetheringEventCallback(@NonNull android.net.ConnectivityManager.OnTetheringEventCallback); + method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void unregisterTetheringEventCallback(@NonNull android.net.ConnectivityManager.OnTetheringEventCallback); field public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC = "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC"; field public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = "android.net.extra.CAPTIVE_PORTAL_USER_AGENT"; field public static final int TETHERING_BLUETOOTH = 2; // 0x2 field public static final int TETHERING_USB = 1; // 0x1 field public static final int TETHERING_WIFI = 0; // 0x0 - field public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13; // 0xd - field public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0 - field public static final int TETHER_ERROR_PROVISION_FAILED = 11; // 0xb + field @Deprecated public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13; // 0xd + field @Deprecated public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0 + field @Deprecated public static final int TETHER_ERROR_PROVISION_FAILED = 11; // 0xb field public static final int TYPE_NONE = -1; // 0xffffffff field @Deprecated public static final int TYPE_WIFI_P2P = 13; // 0xd } @@ -5947,13 +6052,13 @@ package android.net { method public void onTetheringStarted(); } - public static interface ConnectivityManager.OnTetheringEntitlementResultListener { - method public void onTetheringEntitlementResult(int); + @Deprecated public static interface ConnectivityManager.OnTetheringEntitlementResultListener { + method @Deprecated public void onTetheringEntitlementResult(int); } - public abstract static class ConnectivityManager.OnTetheringEventCallback { - ctor public ConnectivityManager.OnTetheringEventCallback(); - method public void onUpstreamChanged(@Nullable android.net.Network); + @Deprecated public abstract static class ConnectivityManager.OnTetheringEventCallback { + ctor @Deprecated public ConnectivityManager.OnTetheringEventCallback(); + method @Deprecated public void onUpstreamChanged(@Nullable android.net.Network); } public class InvalidPacketException extends java.lang.Exception { @@ -6043,6 +6148,8 @@ package android.net { method @NonNull public java.util.List<java.lang.String> getAllInterfaceNames(); method @NonNull public java.util.List<android.net.LinkAddress> getAllLinkAddresses(); method @NonNull public java.util.List<android.net.RouteInfo> getAllRoutes(); + method @Nullable public android.net.Uri getCaptivePortalApiUrl(); + method @Nullable public android.net.CaptivePortalData getCaptivePortalData(); method @NonNull public java.util.List<java.net.InetAddress> getPcscfServers(); method @Nullable public String getTcpBufferSizes(); method @NonNull public java.util.List<java.net.InetAddress> getValidatedPrivateDnsServers(); @@ -6056,9 +6163,12 @@ package android.net { method public boolean isIpv6Provisioned(); method public boolean isProvisioned(); method public boolean isReachable(@NonNull java.net.InetAddress); + method @NonNull public android.net.LinkProperties makeSensitiveFieldsParcelingCopy(); method public boolean removeDnsServer(@NonNull java.net.InetAddress); method public boolean removeLinkAddress(@NonNull android.net.LinkAddress); method public boolean removeRoute(@NonNull android.net.RouteInfo); + method public void setCaptivePortalApiUrl(@Nullable android.net.Uri); + method public void setCaptivePortalData(@Nullable android.net.CaptivePortalData); method public void setPcscfServers(@NonNull java.util.Collection<java.net.InetAddress>); method public void setPrivateDnsServerName(@Nullable String); method public void setTcpBufferSizes(@Nullable String); @@ -6143,6 +6253,7 @@ package android.net { public final class NetworkCapabilities implements android.os.Parcelable { method public boolean deduceRestrictedCapability(); + method @Nullable public String getSSID(); method @NonNull public int[] getTransportTypes(); method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities); method @NonNull public android.net.NetworkCapabilities setSSID(@Nullable String); @@ -6153,7 +6264,7 @@ package android.net { public class NetworkKey implements android.os.Parcelable { ctor public NetworkKey(android.net.WifiKey); - method @Nullable public static android.net.NetworkKey createFromScanResult(@Nullable android.net.wifi.ScanResult); + method @Nullable public static android.net.NetworkKey createFromScanResult(@NonNull android.net.wifi.ScanResult); method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkKey> CREATOR; @@ -6162,6 +6273,22 @@ package android.net { field public final android.net.WifiKey wifiKey; } + public class NetworkPolicyManager { + method @NonNull public android.telephony.SubscriptionPlan[] getSubscriptionPlans(int, @NonNull String); + method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public void registerSubscriptionCallback(@NonNull android.net.NetworkPolicyManager.SubscriptionCallback); + method public void setSubscriptionOverride(int, int, int, long, @NonNull String); + method public void setSubscriptionPlans(int, @NonNull android.telephony.SubscriptionPlan[], @NonNull String); + method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public void unregisterSubscriptionCallback(@NonNull android.net.NetworkPolicyManager.SubscriptionCallback); + field public static final int SUBSCRIPTION_OVERRIDE_CONGESTED = 2; // 0x2 + field public static final int SUBSCRIPTION_OVERRIDE_UNMETERED = 1; // 0x1 + } + + public static class NetworkPolicyManager.SubscriptionCallback { + ctor public NetworkPolicyManager.SubscriptionCallback(); + method public void onSubscriptionOverride(int, int, int); + method public void onSubscriptionPlansChanged(int, @NonNull android.telephony.SubscriptionPlan[]); + } + public class NetworkProvider { ctor public NetworkProvider(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String); method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void declareNetworkRequestUnfulfillable(@NonNull android.net.NetworkRequest); @@ -6188,12 +6315,12 @@ package android.net { } public class NetworkScoreManager { - method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, "android.permission.REQUEST_NETWORK_SCORES"}) public boolean clearScores() throws java.lang.SecurityException; - method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, "android.permission.REQUEST_NETWORK_SCORES"}) public void disableScoring() throws java.lang.SecurityException; - method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, "android.permission.REQUEST_NETWORK_SCORES"}) public String getActiveScorerPackage(); - method @RequiresPermission("android.permission.REQUEST_NETWORK_SCORES") public void registerNetworkScoreCallback(int, int, @NonNull java.util.concurrent.Executor, @NonNull android.net.NetworkScoreManager.NetworkScoreCallback) throws java.lang.SecurityException; - method @RequiresPermission("android.permission.REQUEST_NETWORK_SCORES") public boolean requestScores(@NonNull android.net.NetworkKey[]) throws java.lang.SecurityException; - method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, "android.permission.REQUEST_NETWORK_SCORES"}) public boolean setActiveScorer(String) throws java.lang.SecurityException; + method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, android.Manifest.permission.REQUEST_NETWORK_SCORES}) public boolean clearScores() throws java.lang.SecurityException; + method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, android.Manifest.permission.REQUEST_NETWORK_SCORES}) public void disableScoring() throws java.lang.SecurityException; + method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, android.Manifest.permission.REQUEST_NETWORK_SCORES}) public String getActiveScorerPackage(); + method @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES) public void registerNetworkScoreCallback(int, int, @NonNull java.util.concurrent.Executor, @NonNull android.net.NetworkScoreManager.NetworkScoreCallback) throws java.lang.SecurityException; + method @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES) public boolean requestScores(@NonNull java.util.Collection<android.net.NetworkKey>) throws java.lang.SecurityException; + method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, android.Manifest.permission.REQUEST_NETWORK_SCORES}) public boolean setActiveScorer(String) throws java.lang.SecurityException; method @RequiresPermission(android.Manifest.permission.SCORE_NETWORKS) public boolean updateScores(@NonNull android.net.ScoredNetwork[]) throws java.lang.SecurityException; field @Deprecated public static final String ACTION_CHANGE_ACTIVE = "android.net.scoring.CHANGE_ACTIVE"; field public static final String ACTION_CUSTOM_ENABLE = "android.net.scoring.CUSTOM_ENABLE"; @@ -6208,9 +6335,10 @@ package android.net { field public static final int SCORE_FILTER_SCAN_RESULTS = 2; // 0x2 } - public static interface NetworkScoreManager.NetworkScoreCallback { - method public void clearScores(); - method public void updateScores(@NonNull java.util.List<android.net.ScoredNetwork>); + public abstract static class NetworkScoreManager.NetworkScoreCallback { + ctor public NetworkScoreManager.NetworkScoreCallback(); + method public abstract void onScoresInvalidated(); + method public abstract void onScoresUpdated(@NonNull java.util.Collection<android.net.ScoredNetwork>); } public abstract class NetworkSpecifier { @@ -6319,17 +6447,63 @@ package android.net { method @NonNull public android.net.StaticIpConfiguration.Builder setIpAddress(@Nullable android.net.LinkAddress); } - public final class StringNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable { - ctor public StringNetworkSpecifier(@NonNull String); - method public int describeContents(); + public final class TelephonyNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable { method public boolean satisfiedBy(android.net.NetworkSpecifier); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.StringNetworkSpecifier> CREATOR; - 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 TetheringManager { + method public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.TetheringEventCallback); + method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void requestLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.OnTetheringEntitlementResultListener); + method public void stopAllTethering(); + method public void stopTethering(int); + method public void unregisterTetheringEventCallback(@NonNull android.net.TetheringManager.TetheringEventCallback); + field public static final String ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED"; + field public static final String EXTRA_ACTIVE_LOCAL_ONLY = "android.net.extra.ACTIVE_LOCAL_ONLY"; + field public static final String EXTRA_ACTIVE_TETHER = "tetherArray"; + field public static final String EXTRA_AVAILABLE_TETHER = "availableArray"; + field public static final String EXTRA_ERRORED_TETHER = "erroredArray"; + field public static final int TETHERING_BLUETOOTH = 2; // 0x2 + field public static final int TETHERING_INVALID = -1; // 0xffffffff + field public static final int TETHERING_USB = 1; // 0x1 + field public static final int TETHERING_WIFI = 0; // 0x0 + field public static final int TETHERING_WIFI_P2P = 3; // 0x3 + field public static final int TETHER_ERROR_DHCPSERVER_ERROR = 12; // 0xc + field public static final int TETHER_ERROR_DISABLE_NAT_ERROR = 9; // 0x9 + field public static final int TETHER_ERROR_ENABLE_NAT_ERROR = 8; // 0x8 + field public static final int TETHER_ERROR_ENTITLEMENT_UNKNOWN = 13; // 0xd + field public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10; // 0xa + field public static final int TETHER_ERROR_MASTER_ERROR = 5; // 0x5 + field public static final int TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION = 15; // 0xf + field public static final int TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION = 14; // 0xe + field public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0 + field public static final int TETHER_ERROR_PROVISION_FAILED = 11; // 0xb + field public static final int TETHER_ERROR_SERVICE_UNAVAIL = 2; // 0x2 + field public static final int TETHER_ERROR_TETHER_IFACE_ERROR = 6; // 0x6 + field public static final int TETHER_ERROR_UNAVAIL_IFACE = 4; // 0x4 + field public static final int TETHER_ERROR_UNKNOWN_IFACE = 1; // 0x1 + field public static final int TETHER_ERROR_UNSUPPORTED = 3; // 0x3 + field public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = 7; // 0x7 + } + + public static interface TetheringManager.OnTetheringEntitlementResultListener { + method public void onTetheringEntitlementResult(int); + } + + public abstract static class TetheringManager.TetheringEventCallback { + ctor public TetheringManager.TetheringEventCallback(); + method public void onError(@NonNull String, int); + method @Deprecated public void onTetherableInterfaceRegexpsChanged(@NonNull android.net.TetheringManager.TetheringInterfaceRegexps); + method public void onTetherableInterfacesChanged(@NonNull java.util.List<java.lang.String>); + method public void onTetheredInterfacesChanged(@NonNull java.util.List<java.lang.String>); + method public void onTetheringSupported(boolean); + method public void onUpstreamChanged(@Nullable android.net.Network); + } + + @Deprecated public static class TetheringManager.TetheringInterfaceRegexps { + ctor @Deprecated public TetheringManager.TetheringInterfaceRegexps(@NonNull String[], @NonNull String[], @NonNull String[]); + method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableBluetoothRegexs(); + method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableUsbRegexs(); + method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableWifiRegexs(); } public class TrafficStats { @@ -7332,6 +7506,7 @@ package android.net.wifi { public class WifiManager { method @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE) public void addOnWifiUsabilityStatsListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.OnWifiUsabilityStatsListener); method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void allowAutojoin(int, boolean); + method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void allowAutojoinGlobal(boolean); method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void allowAutojoinPasspoint(@NonNull String, boolean); method @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE) public void clearWifiConnectedNetworkScorer(); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void connect(@NonNull android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiManager.ActionListener); @@ -7347,6 +7522,7 @@ package android.net.wifi { method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public String[] getFactoryMacAddresses(); method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.Map<android.net.wifi.hotspot2.OsuProvider,java.util.List<android.net.wifi.ScanResult>> getMatchingOsuProviders(@Nullable java.util.List<android.net.wifi.ScanResult>); method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.Map<android.net.wifi.hotspot2.OsuProvider,android.net.wifi.hotspot2.PasspointConfiguration> getMatchingPasspointConfigsForOsuProviders(@NonNull java.util.Set<android.net.wifi.hotspot2.OsuProvider>); + method @NonNull @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE}) public java.util.Map<android.net.wifi.WifiNetworkSuggestion,java.util.List<android.net.wifi.ScanResult>> getMatchingScanResults(@NonNull java.util.List<android.net.wifi.WifiNetworkSuggestion>, @Nullable java.util.List<android.net.wifi.ScanResult>); method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.READ_WIFI_CREDENTIAL}) public java.util.List<android.net.wifi.WifiConfiguration> getPrivilegedConfiguredNetworks(); method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public android.net.wifi.SoftApConfiguration getSoftApConfiguration(); method public int getVerboseLoggingLevel(); @@ -8443,10 +8619,14 @@ package android.os { method @RequiresPermission(allOf={android.Manifest.permission.READ_DREAM_STATE, android.Manifest.permission.WRITE_DREAM_STATE}) public void dream(long); method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public boolean forceSuspend(); method @RequiresPermission(android.Manifest.permission.POWER_SAVER) public int getPowerSaveModeTrigger(); + method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isAmbientDisplayAvailable(); + method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isAmbientDisplaySuppressed(); + method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isAmbientDisplaySuppressedForToken(@NonNull String); method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setAdaptivePowerSaveEnabled(boolean); method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setAdaptivePowerSavePolicy(@NonNull android.os.BatterySaverPolicyConfig); method @RequiresPermission(android.Manifest.permission.POWER_SAVER) public boolean setDynamicPowerSaveHint(boolean, int); method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setPowerSaveModeEnabled(boolean); + method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void suppressAmbientDisplay(@NonNull String, boolean); method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.USER_ACTIVITY}) public void userActivity(long, int, int); field public static final int POWER_SAVE_MODE_TRIGGER_DYNAMIC = 1; // 0x1 field public static final int POWER_SAVE_MODE_TRIGGER_PERCENTAGE = 0; // 0x0 @@ -8567,7 +8747,6 @@ package android.os { method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getTelephonyRcsMessageServiceRegisterer(); method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getTelephonyRegistryServiceRegisterer(); method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getTelephonyServiceRegisterer(); - method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getWindowServiceRegisterer(); } public static class TelephonyServiceManager.ServiceNotFoundException extends java.lang.Exception { @@ -9971,6 +10150,7 @@ package android.service.euicc { public abstract class EuiccService extends android.app.Service { ctor public EuiccService(); method public void dump(@NonNull java.io.PrintWriter); + method public int encodeSmdxSubjectAndReasonCode(@Nullable String, @Nullable String) throws java.lang.IllegalArgumentException, java.lang.NumberFormatException, java.lang.UnsupportedOperationException; method @CallSuper public android.os.IBinder onBind(android.content.Intent); method public abstract int onDeleteSubscription(int, String); method public android.service.euicc.DownloadSubscriptionResult onDownloadSubscription(int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean, boolean, @Nullable android.os.Bundle); @@ -10069,6 +10249,7 @@ package android.service.notification { field @NonNull public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR; field public static final String KEY_CONTEXTUAL_ACTIONS = "key_contextual_actions"; field public static final String KEY_IMPORTANCE = "key_importance"; + field public static final String KEY_NOT_CONVERSATION = "key_not_conversation"; field public static final String KEY_PEOPLE = "key_people"; field public static final String KEY_RANKING_SCORE = "key_ranking_score"; field public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria"; @@ -10330,6 +10511,14 @@ package android.service.trust { } +package android.service.voice { + + public class VoiceInteractionService extends android.app.Service { + method @NonNull @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public final android.media.voice.KeyphraseModelManager createKeyphraseModelManager(); + } + +} + package android.service.wallpaper { public class WallpaperService.Engine { @@ -10675,6 +10864,13 @@ package android.telephony { field public static final int TRANSPORT_TYPE_INVALID = -1; // 0xffffffff } + public static final class AccessNetworkConstants.NgranBands { + method public static int getFrequencyRangeGroup(int); + field public static final int FREQUENCY_RANGE_GROUP_1 = 1; // 0x1 + field public static final int FREQUENCY_RANGE_GROUP_2 = 2; // 0x2 + field public static final int FREQUENCY_RANGE_GROUP_UNKNOWN = 0; // 0x0 + } + public final class BarringInfo implements android.os.Parcelable { ctor public BarringInfo(); method @NonNull public android.telephony.BarringInfo createLocationInfoSanitizedCopy(); @@ -11712,6 +11908,7 @@ package android.telephony { } public final class SmsManager { + method @RequiresPermission(android.Manifest.permission.ACCESS_MESSAGES_ON_ICC) public boolean copyMessageToIcc(@Nullable byte[], @NonNull byte[], int); method @RequiresPermission(android.Manifest.permission.ACCESS_MESSAGES_ON_ICC) public boolean deleteMessageFromIcc(int); method public boolean disableCellBroadcastRange(int, int, int); method public boolean enableCellBroadcastRange(int, int, int); @@ -11729,6 +11926,7 @@ package android.telephony { public class SmsMessage { method @Nullable public static android.telephony.SmsMessage createFromNativeSmsSubmitPdu(@NonNull byte[], boolean); + method @Nullable public static android.telephony.SmsMessage.SubmitPdu getSmsPdu(int, int, @Nullable String, @NonNull String, @NonNull String, long); method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public static byte[] getSubmitPduEncodedMessage(boolean, @NonNull String, @NonNull String, int, int, int, int, int, int); } @@ -11881,7 +12079,6 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean isIccLockEnabled(); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isIdle(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isInEmergencySmsMode(); - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isManualNetworkSelectionAllowed(); method public boolean isModemEnabledForSlot(int); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isOffhook(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isOpportunisticNetworkEnabled(); @@ -11894,6 +12091,7 @@ package android.telephony { method public boolean modifyDevicePolicyOverrideApn(@NonNull android.content.Context, int, @NonNull android.telephony.data.ApnSetting); method public boolean needsOtaServiceProvisioning(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyOtaEmergencyNumberDbInstalled(); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyUserActivity(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void reportDefaultNetworkStatus(boolean); method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.MODIFY_PHONE_STATE}) public void requestCellInfoUpdate(@NonNull android.os.WorkSource, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback); @@ -11917,6 +12115,7 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataRoamingEnabled(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setIccLockEnabled(boolean, @NonNull String); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMultiSimCarrierRestriction(boolean); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setNetworkSelectionModeManual(@NonNull String, int, boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunisticNetworkState(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setPolicyDataEnabled(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setPreferredNetworkTypeBitmask(long); @@ -11951,6 +12150,7 @@ package android.telephony { field public static final String ACTION_EMERGENCY_CALLBACK_MODE_CHANGED = "android.intent.action.EMERGENCY_CALLBACK_MODE_CHANGED"; field public static final String ACTION_EMERGENCY_CALL_STATE_CHANGED = "android.intent.action.EMERGENCY_CALL_STATE_CHANGED"; field public static final String ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE = "com.android.omadm.service.CONFIGURATION_UPDATE"; + field public static final String ACTION_SERVICE_PROVIDERS_UPDATED = "android.telephony.action.SERVICE_PROVIDERS_UPDATED"; field public static final String ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS = "android.telephony.action.SHOW_NOTICE_ECM_BLOCK_OTHERS"; field public static final String ACTION_SIM_APPLICATION_STATE_CHANGED = "android.telephony.action.SIM_APPLICATION_STATE_CHANGED"; field public static final String ACTION_SIM_CARD_STATE_CHANGED = "android.telephony.action.SIM_CARD_STATE_CHANGED"; @@ -11973,6 +12173,7 @@ package android.telephony { field public static final String EXTRA_APN_PROTOCOL_INT = "apnProtoInt"; field @Deprecated public static final String EXTRA_APN_TYPE = "apnType"; field public static final String EXTRA_APN_TYPE_INT = "apnTypeInt"; + field public static final String EXTRA_DATA_SPN = "android.telephony.extra.DATA_SPN"; field public static final String EXTRA_DEFAULT_NETWORK_AVAILABLE = "defaultNetworkAvailable"; field public static final String EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE = "android.telephony.extra.DEFAULT_SUBSCRIPTION_SELECT_TYPE"; field public static final int EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_ALL = 4; // 0x4 @@ -11985,17 +12186,22 @@ package android.telephony { field public static final String EXTRA_PCO_VALUE = "pcoValue"; field public static final String EXTRA_PHONE_IN_ECM_STATE = "android.telephony.extra.PHONE_IN_ECM_STATE"; field public static final String EXTRA_PHONE_IN_EMERGENCY_CALL = "android.telephony.extra.PHONE_IN_EMERGENCY_CALL"; + field public static final String EXTRA_PLMN = "android.telephony.extra.PLMN"; field public static final String EXTRA_REDIRECTION_URL = "redirectionUrl"; + field public static final String EXTRA_SHOW_PLMN = "android.telephony.extra.SHOW_PLMN"; + field public static final String EXTRA_SHOW_SPN = "android.telephony.extra.SHOW_SPN"; field public static final String EXTRA_SIM_COMBINATION_NAMES = "android.telephony.extra.SIM_COMBINATION_NAMES"; field public static final String EXTRA_SIM_COMBINATION_WARNING_TYPE = "android.telephony.extra.SIM_COMBINATION_WARNING_TYPE"; field public static final int EXTRA_SIM_COMBINATION_WARNING_TYPE_DUAL_CDMA = 1; // 0x1 field public static final int EXTRA_SIM_COMBINATION_WARNING_TYPE_NONE = 0; // 0x0 field public static final String EXTRA_SIM_STATE = "android.telephony.extra.SIM_STATE"; + field public static final String EXTRA_SPN = "android.telephony.extra.SPN"; field public static final String EXTRA_VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL = "android.telephony.extra.VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL"; field public static final String EXTRA_VOICEMAIL_SCRAMBLED_PIN_STRING = "android.telephony.extra.VOICEMAIL_SCRAMBLED_PIN_STRING"; field public static final int INVALID_EMERGENCY_NUMBER_DB_VERSION = -1; // 0xffffffff field public static final int KEY_TYPE_EPDG = 1; // 0x1 field public static final int KEY_TYPE_WLAN = 2; // 0x2 + field public static final String MODEM_ACTIVITY_RESULT_KEY = "controller_activity"; field public static final long NETWORK_TYPE_BITMASK_1xRTT = 64L; // 0x40L field public static final long NETWORK_TYPE_BITMASK_CDMA = 8L; // 0x8L field public static final long NETWORK_TYPE_BITMASK_EDGE = 2L; // 0x2L @@ -12040,10 +12246,32 @@ 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 notifyActiveDataSubIdChanged(int); method public void notifyBarringInfoChanged(int, int, @NonNull android.telephony.BarringInfo); + method public void notifyCallForwardingChanged(int, boolean); + method public void notifyCallQualityChanged(int, int, @NonNull android.telephony.CallQuality, int); + method public void notifyCallStateChanged(int, int, int, @Nullable String); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyCallStateChangedForAllSubscriptions(int, @Nullable String); method public void notifyCarrierNetworkChange(boolean); + method public void notifyCellInfoChanged(int, @NonNull java.util.List<android.telephony.CellInfo>); + method public void notifyCellLocation(int, @NonNull android.telephony.CellIdentity); + method public void notifyDataActivationStateChanged(int, int, int); + method public void notifyDataActivityChanged(int, int); + method public void notifyDataConnectionForSubscriber(int, int, int, @Nullable android.telephony.PreciseDataConnectionState); + method public void notifyDisconnectCause(int, int, int, int); + method public void notifyEmergencyNumberList(int, int); + method public void notifyImsDisconnectCause(int, @NonNull android.telephony.ims.ImsReasonInfo); + method public void notifyMessageWaitingChanged(int, int, boolean); + method public void notifyPhoneCapabilityChanged(@NonNull android.telephony.PhoneCapability); + method public void notifyPreciseCallState(int, int, int, int, int); + method public void notifyPreciseDataConnectionFailed(int, int, int, @Nullable String, int); + method public void notifyRadioPowerStateChanged(int, int, int); method public void notifyRegistrationFailed(int, int, @NonNull android.telephony.CellIdentity, @NonNull String, int, int, int); + method public void notifyServiceStateChanged(int, int, @NonNull android.telephony.ServiceState); + method public void notifySignalStrengthChanged(int, int, @NonNull android.telephony.SignalStrength); + method public void notifySrvccStateChanged(int, int); + method public void notifyUserMobileDataStateChanged(int, int, boolean); + method public void notifyVoiceActivationStateChanged(int, int, int); method public void removeOnOpportunisticSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener); method public void removeOnSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnSubscriptionsChangedListener); } @@ -12512,6 +12740,7 @@ package android.telephony.ims { field public static final String EXTRA_DIALSTRING = "dialstring"; field public static final String EXTRA_DISPLAY_TEXT = "DisplayText"; field public static final String EXTRA_EMERGENCY_CALL = "e_call"; + field public static final String EXTRA_FORWARDED_NUMBER = "android.telephony.ims.extra.FORWARDED_NUMBER"; field public static final String EXTRA_IS_CALL_PULL = "CallPull"; field public static final String EXTRA_OI = "oi"; field public static final String EXTRA_OIR = "oir"; @@ -12623,6 +12852,9 @@ package android.telephony.ims { method @NonNull public android.telephony.ims.ImsMmTelManager getImsMmTelManager(int); method @NonNull public android.telephony.ims.ImsRcsManager getImsRcsManager(int); field public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION = "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION"; + field public static final String ACTION_WFC_IMS_REGISTRATION_ERROR = "android.telephony.ims.action.WFC_IMS_REGISTRATION_ERROR"; + field public static final String EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE = "android.telephony.ims.extra.WFC_REGISTRATION_FAILURE_MESSAGE"; + field public static final String EXTRA_WFC_REGISTRATION_FAILURE_TITLE = "android.telephony.ims.extra.WFC_REGISTRATION_FAILURE_TITLE"; } public class ImsMmTelManager implements android.telephony.ims.RegistrationManager { @@ -13298,6 +13530,7 @@ package android.telephony.ims.stub { method public int transact(android.os.Bundle); method public int updateCallBarring(int, int, String[]); method public int updateCallBarringForServiceClass(int, int, String[], int); + method public int updateCallBarringWithPassword(int, int, @Nullable String[], int, @NonNull String); method public int updateCallForward(int, int, String, int, int); method public int updateCallWaiting(boolean, int); method public int updateClip(boolean); @@ -13616,8 +13849,8 @@ package android.webkit { } public interface PacProcessor { + method @Nullable public String findProxyForUrl(@NonNull String); method @NonNull public static android.webkit.PacProcessor getInstance(); - method @Nullable public String makeProxyRequest(@NonNull String); method public boolean setProxyScript(@NonNull String); } @@ -13967,4 +14200,3 @@ package android.webkit { } } - diff --git a/api/system-lint-baseline.txt b/api/system-lint-baseline.txt index fde6bb3424f7..23e1ed75998a 100644 --- a/api/system-lint-baseline.txt +++ b/api/system-lint-baseline.txt @@ -8,6 +8,15 @@ ActionValue: android.location.Location#EXTRA_NO_GPS_LOCATION: ActionValue: android.net.wifi.WifiManager#ACTION_LINK_CONFIGURATION_CHANGED: +// Tethering broadcast action / extras cannot change name for backwards compatibility +ActionValue: android.net.TetheringManager#ACTION_TETHER_STATE_CHANGED: + Inconsistent action value; expected `android.net.action.TETHER_STATE_CHANGED`, was `android.net.conn.TETHER_STATE_CHANGED` +ActionValue: android.net.TetheringManager#EXTRA_ACTIVE_TETHER: + Inconsistent extra value; expected `android.net.extra.ACTIVE_TETHER`, was `tetherArray` +ActionValue: android.net.TetheringManager#EXTRA_AVAILABLE_TETHER: + Inconsistent extra value; expected `android.net.extra.AVAILABLE_TETHER`, was `availableArray` +ActionValue: android.net.TetheringManager#EXTRA_ERRORED_TETHER: + Inconsistent extra value; expected `android.net.extra.ERRORED_TETHER`, was `erroredArray` ArrayReturn: android.bluetooth.BluetoothCodecStatus#BluetoothCodecStatus(android.bluetooth.BluetoothCodecConfig, android.bluetooth.BluetoothCodecConfig[], android.bluetooth.BluetoothCodecConfig[]) parameter #1: Method parameter should be Collection<BluetoothCodecConfig> (or subclass) instead of raw array; was `android.bluetooth.BluetoothCodecConfig[]` diff --git a/api/test-current.txt b/api/test-current.txt index ccadb06bc936..ca245077ce30 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -19,7 +19,7 @@ package android { field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS"; field public static final String TEST_MANAGE_ROLLBACKS = "android.permission.TEST_MANAGE_ROLLBACKS"; field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG"; - field public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE"; + field @Deprecated public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE"; field public static final String WRITE_OBB = "android.permission.WRITE_OBB"; } @@ -742,6 +742,10 @@ package android.content { field @Nullable public final android.util.ArraySet<android.content.ComponentName> whitelistedComponents; } + public abstract class ContentProvider implements android.content.ComponentCallbacks2 { + method @NonNull public static android.os.UserHandle getUserHandleFromUri(@NonNull android.net.Uri); + } + public class ContentProviderClient implements java.lang.AutoCloseable { method @RequiresPermission(android.Manifest.permission.REMOVE_TASKS) public void setDetectNotResponding(long); } @@ -754,7 +758,6 @@ package android.content { method @NonNull public android.content.Context createContextAsUser(@NonNull android.os.UserHandle, int); method @NonNull public android.content.Context createPackageContextAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull public java.io.File getCrateDir(@NonNull String); - method public abstract android.view.Display getDisplay(); method public abstract int getDisplayId(); method public android.os.UserHandle getUser(); method public int getUserId(); @@ -775,7 +778,6 @@ package android.content { } public class ContextWrapper extends android.content.Context { - method public android.view.Display getDisplay(); method public int getDisplayId(); } @@ -1395,6 +1397,7 @@ package android.media.audiopolicy { field public static final int RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET = 2; // 0x2 field public static final int RULE_MATCH_ATTRIBUTE_USAGE = 1; // 0x1 field public static final int RULE_MATCH_UID = 4; // 0x4 + field public static final int RULE_MATCH_USERID = 8; // 0x8 } public static class AudioMixingRule.Builder { @@ -1415,9 +1418,11 @@ package android.media.audiopolicy { method public int getFocusDuckingBehavior(); method public int getStatus(); method public boolean removeUidDeviceAffinity(int); + method public boolean removeUserIdDeviceAffinity(int); method public int setFocusDuckingBehavior(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException; method public void setRegistration(String); method public boolean setUidDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>); + method public boolean setUserIdDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>); method public String toLogFriendlyString(); field public static final int FOCUS_POLICY_DUCKING_DEFAULT = 0; // 0x0 field public static final int FOCUS_POLICY_DUCKING_IN_APP = 0; // 0x0 @@ -1515,6 +1520,32 @@ package android.net { field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2 } + public final class CaptivePortalData implements android.os.Parcelable { + method public int describeContents(); + method public long getByteLimit(); + method public long getExpiryTimeMillis(); + method public long getRefreshTimeMillis(); + method @Nullable public android.net.Uri getUserPortalUrl(); + method @Nullable public android.net.Uri getVenueInfoUrl(); + method public boolean isCaptive(); + method public boolean isSessionExtendable(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.CaptivePortalData> CREATOR; + } + + public static class CaptivePortalData.Builder { + ctor public CaptivePortalData.Builder(); + ctor public CaptivePortalData.Builder(@Nullable android.net.CaptivePortalData); + method @NonNull public android.net.CaptivePortalData build(); + method @NonNull public android.net.CaptivePortalData.Builder setBytesRemaining(long); + method @NonNull public android.net.CaptivePortalData.Builder setCaptive(boolean); + method @NonNull public android.net.CaptivePortalData.Builder setExpiryTime(long); + method @NonNull public android.net.CaptivePortalData.Builder setRefreshTime(long); + method @NonNull public android.net.CaptivePortalData.Builder setSessionExtendable(boolean); + method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri); + method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri); + } + public class ConnectivityManager { method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(@NonNull android.net.Network, @NonNull android.os.Bundle); field public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC = "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC"; @@ -1545,6 +1576,8 @@ package android.net { ctor public LinkProperties(@Nullable android.net.LinkProperties); method public boolean addDnsServer(@NonNull java.net.InetAddress); method public boolean addLinkAddress(@NonNull android.net.LinkAddress); + method @Nullable public android.net.Uri getCaptivePortalApiUrl(); + method @Nullable public android.net.CaptivePortalData getCaptivePortalData(); method @NonNull public java.util.List<java.net.InetAddress> getPcscfServers(); method @Nullable public String getTcpBufferSizes(); method @NonNull public java.util.List<java.net.InetAddress> getValidatedPrivateDnsServers(); @@ -1555,9 +1588,12 @@ package android.net { method public boolean isIpv6Provisioned(); method public boolean isProvisioned(); method public boolean isReachable(@NonNull java.net.InetAddress); + method @NonNull public android.net.LinkProperties makeSensitiveFieldsParcelingCopy(); method public boolean removeDnsServer(@NonNull java.net.InetAddress); method public boolean removeLinkAddress(@NonNull android.net.LinkAddress); method public boolean removeRoute(@NonNull android.net.RouteInfo); + method public void setCaptivePortalApiUrl(@Nullable android.net.Uri); + method public void setCaptivePortalData(@Nullable android.net.CaptivePortalData); method public void setPcscfServers(@NonNull java.util.Collection<java.net.InetAddress>); method public void setPrivateDnsServerName(@Nullable String); method public void setTcpBufferSizes(@Nullable String); @@ -1629,6 +1665,61 @@ package android.net { method public void teardownTestNetwork(@NonNull android.net.Network); } + public class TetheringManager { + method public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.TetheringEventCallback); + method @RequiresPermission("android.permission.TETHER_PRIVILEGED") public void requestLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.OnTetheringEntitlementResultListener); + method public void stopAllTethering(); + method public void stopTethering(int); + method public void unregisterTetheringEventCallback(@NonNull android.net.TetheringManager.TetheringEventCallback); + field public static final String ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED"; + field public static final String EXTRA_ACTIVE_LOCAL_ONLY = "android.net.extra.ACTIVE_LOCAL_ONLY"; + field public static final String EXTRA_ACTIVE_TETHER = "tetherArray"; + field public static final String EXTRA_AVAILABLE_TETHER = "availableArray"; + field public static final String EXTRA_ERRORED_TETHER = "erroredArray"; + field public static final int TETHERING_BLUETOOTH = 2; // 0x2 + field public static final int TETHERING_INVALID = -1; // 0xffffffff + field public static final int TETHERING_USB = 1; // 0x1 + field public static final int TETHERING_WIFI = 0; // 0x0 + field public static final int TETHERING_WIFI_P2P = 3; // 0x3 + field public static final int TETHER_ERROR_DHCPSERVER_ERROR = 12; // 0xc + field public static final int TETHER_ERROR_DISABLE_NAT_ERROR = 9; // 0x9 + field public static final int TETHER_ERROR_ENABLE_NAT_ERROR = 8; // 0x8 + field public static final int TETHER_ERROR_ENTITLEMENT_UNKNOWN = 13; // 0xd + field public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10; // 0xa + field public static final int TETHER_ERROR_MASTER_ERROR = 5; // 0x5 + field public static final int TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION = 15; // 0xf + field public static final int TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION = 14; // 0xe + field public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0 + field public static final int TETHER_ERROR_PROVISION_FAILED = 11; // 0xb + field public static final int TETHER_ERROR_SERVICE_UNAVAIL = 2; // 0x2 + field public static final int TETHER_ERROR_TETHER_IFACE_ERROR = 6; // 0x6 + field public static final int TETHER_ERROR_UNAVAIL_IFACE = 4; // 0x4 + field public static final int TETHER_ERROR_UNKNOWN_IFACE = 1; // 0x1 + field public static final int TETHER_ERROR_UNSUPPORTED = 3; // 0x3 + field public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = 7; // 0x7 + } + + public static interface TetheringManager.OnTetheringEntitlementResultListener { + method public void onTetheringEntitlementResult(int); + } + + public abstract static class TetheringManager.TetheringEventCallback { + ctor public TetheringManager.TetheringEventCallback(); + method public void onError(@NonNull String, int); + method @Deprecated public void onTetherableInterfaceRegexpsChanged(@NonNull android.net.TetheringManager.TetheringInterfaceRegexps); + method public void onTetherableInterfacesChanged(@NonNull java.util.List<java.lang.String>); + method public void onTetheredInterfacesChanged(@NonNull java.util.List<java.lang.String>); + method public void onTetheringSupported(boolean); + method public void onUpstreamChanged(@Nullable android.net.Network); + } + + @Deprecated public static class TetheringManager.TetheringInterfaceRegexps { + ctor @Deprecated public TetheringManager.TetheringInterfaceRegexps(@NonNull String[], @NonNull String[], @NonNull String[]); + method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableBluetoothRegexs(); + method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableUsbRegexs(); + method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableWifiRegexs(); + } + public class TrafficStats { method public static long getLoopbackRxBytes(); method public static long getLoopbackRxPackets(); @@ -3121,6 +3212,8 @@ package android.telecom { method @NonNull public android.telecom.ConnectionRequest.Builder setAccountHandle(@NonNull android.telecom.PhoneAccountHandle); method @NonNull public android.telecom.ConnectionRequest.Builder setAddress(@NonNull android.net.Uri); method @NonNull public android.telecom.ConnectionRequest.Builder setExtras(@NonNull android.os.Bundle); + method @NonNull public android.telecom.ConnectionRequest.Builder setIsAdhocConferenceCall(boolean); + method @NonNull public android.telecom.ConnectionRequest.Builder setParticipants(@Nullable java.util.List<android.net.Uri>); method @NonNull public android.telecom.ConnectionRequest.Builder setRttPipeFromInCall(@NonNull android.os.ParcelFileDescriptor); method @NonNull public android.telecom.ConnectionRequest.Builder setRttPipeToInCall(@NonNull android.os.ParcelFileDescriptor); method @NonNull public android.telecom.ConnectionRequest.Builder setShouldShowIncomingCallUi(boolean); @@ -3160,6 +3253,13 @@ package android.telephony { field public static final int TRANSPORT_TYPE_INVALID = -1; // 0xffffffff } + public static final class AccessNetworkConstants.NgranBands { + method public static int getFrequencyRangeGroup(int); + field public static final int FREQUENCY_RANGE_GROUP_1 = 1; // 0x1 + field public static final int FREQUENCY_RANGE_GROUP_2 = 2; // 0x2 + field public static final int FREQUENCY_RANGE_GROUP_UNKNOWN = 0; // 0x0 + } + 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>); @@ -3336,6 +3436,39 @@ package android.telephony { field public static final int UNKNOWN_CARRIER_ID_LIST_VERSION = -1; // 0xffffffff } + 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 notifyActiveDataSubIdChanged(int); + method public void notifyBarringInfoChanged(int, int, @NonNull android.telephony.BarringInfo); + method public void notifyCallForwardingChanged(int, boolean); + method public void notifyCallQualityChanged(int, int, @NonNull android.telephony.CallQuality, int); + method public void notifyCallStateChanged(int, int, int, @Nullable String); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyCallStateChangedForAllSubscriptions(int, @Nullable String); + method public void notifyCarrierNetworkChange(boolean); + method public void notifyCellInfoChanged(int, @NonNull java.util.List<android.telephony.CellInfo>); + method public void notifyCellLocation(int, @NonNull android.telephony.CellIdentity); + method public void notifyDataActivationStateChanged(int, int, int); + method public void notifyDataActivityChanged(int, int); + method public void notifyDataConnectionForSubscriber(int, int, int, @Nullable android.telephony.PreciseDataConnectionState); + method public void notifyDisconnectCause(int, int, int, int); + method public void notifyEmergencyNumberList(int, int); + method public void notifyImsDisconnectCause(int, @NonNull android.telephony.ims.ImsReasonInfo); + method public void notifyMessageWaitingChanged(int, int, boolean); + method public void notifyPhoneCapabilityChanged(@NonNull android.telephony.PhoneCapability); + method public void notifyPreciseCallState(int, int, int, int, int); + method public void notifyPreciseDataConnectionFailed(int, int, int, @Nullable String, int); + method public void notifyRadioPowerStateChanged(int, int, int); + method public void notifyRegistrationFailed(int, int, @NonNull android.telephony.CellIdentity, @NonNull String, int, int, int); + method public void notifyServiceStateChanged(int, int, @NonNull android.telephony.ServiceState); + method public void notifySignalStrengthChanged(int, int, @NonNull android.telephony.SignalStrength); + method public void notifySrvccStateChanged(int, int); + method public void notifyUserMobileDataStateChanged(int, int, boolean); + method public void notifyVoiceActivationStateChanged(int, int, int); + method public void removeOnOpportunisticSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener); + method public void removeOnSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnSubscriptionsChangedListener); + } + } package android.telephony.emergency { @@ -3445,6 +3578,7 @@ package android.telephony.ims { field public static final String EXTRA_DIALSTRING = "dialstring"; field public static final String EXTRA_DISPLAY_TEXT = "DisplayText"; field public static final String EXTRA_EMERGENCY_CALL = "e_call"; + field public static final String EXTRA_FORWARDED_NUMBER = "android.telephony.ims.extra.FORWARDED_NUMBER"; field public static final String EXTRA_IS_CALL_PULL = "CallPull"; field public static final String EXTRA_OEM_EXTRAS = "android.telephony.ims.extra.OEM_EXTRAS"; field public static final String EXTRA_OI = "oi"; @@ -3557,6 +3691,9 @@ package android.telephony.ims { method @NonNull public android.telephony.ims.ImsMmTelManager getImsMmTelManager(int); method @NonNull public android.telephony.ims.ImsRcsManager getImsRcsManager(int); field public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION = "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION"; + field public static final String ACTION_WFC_IMS_REGISTRATION_ERROR = "android.telephony.ims.action.WFC_IMS_REGISTRATION_ERROR"; + field public static final String EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE = "android.telephony.ims.extra.WFC_REGISTRATION_FAILURE_MESSAGE"; + field public static final String EXTRA_WFC_REGISTRATION_FAILURE_TITLE = "android.telephony.ims.extra.WFC_REGISTRATION_FAILURE_TITLE"; } public class ImsMmTelManager implements android.telephony.ims.RegistrationManager { @@ -4180,6 +4317,7 @@ package android.telephony.ims.stub { method public int transact(android.os.Bundle); method public int updateCallBarring(int, int, String[]); method public int updateCallBarringForServiceClass(int, int, String[], int); + method public int updateCallBarringWithPassword(int, int, @Nullable String[], int, @NonNull String); method public int updateCallForward(int, int, String, int, int); method public int updateCallWaiting(boolean, int); method public int updateClip(boolean); @@ -4467,22 +4605,6 @@ package android.view { method public abstract String asyncImpl() default ""; } - public class SurfaceControlViewHost { - ctor public SurfaceControlViewHost(@NonNull android.content.Context, @NonNull android.view.Display, @Nullable android.os.IBinder); - method public void addView(android.view.View, android.view.WindowManager.LayoutParams); - method public void dispose(); - method @Nullable public android.view.SurfaceControlViewHost.SurfacePackage getSurfacePackage(); - method public void relayout(android.view.WindowManager.LayoutParams); - } - - public class SurfaceControlViewHost.SurfacePackage { - method @NonNull public android.view.SurfaceControl getSurfaceControl(); - } - - public class SurfaceView extends android.view.View { - method @Nullable public android.os.IBinder getInputToken(); - } - @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback { method public android.view.View getTooltipView(); method public boolean isAutofilled(); @@ -4527,7 +4649,7 @@ package android.view { field public static final int ACCESSIBILITY_TITLE_CHANGED = 33554432; // 0x2000000 field public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 64; // 0x40 field public CharSequence accessibilityTitle; - field @android.view.ViewDebug.ExportedProperty(flagMapping={@android.view.ViewDebug.FlagToString(mask=0x1, equals=0x1, name="FAKE_HARDWARE_ACCELERATED"), @android.view.ViewDebug.FlagToString(mask=0x2, equals=0x2, name="FORCE_HARDWARE_ACCELERATED"), @android.view.ViewDebug.FlagToString(mask=0x4, equals=0x4, name="WANTS_OFFSET_NOTIFICATIONS"), @android.view.ViewDebug.FlagToString(mask=0x10, equals=0x10, name="SHOW_FOR_ALL_USERS"), @android.view.ViewDebug.FlagToString(mask=android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION, equals=android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION, name="NO_MOVE_ANIMATION"), @android.view.ViewDebug.FlagToString(mask=0x80, equals=0x80, name="COMPATIBLE_WINDOW"), @android.view.ViewDebug.FlagToString(mask=0x100, equals=0x100, name="SYSTEM_ERROR"), @android.view.ViewDebug.FlagToString(mask=0x800, equals=0x800, name="DISABLE_WALLPAPER_TOUCH_EVENTS"), @android.view.ViewDebug.FlagToString(mask=0x1000, equals=0x1000, name="FORCE_STATUS_BAR_VISIBLE"), @android.view.ViewDebug.FlagToString(mask=0x2000, equals=0x2000, name="PRESERVE_GEOMETRY"), @android.view.ViewDebug.FlagToString(mask=0x4000, equals=0x4000, name="FORCE_DECOR_VIEW_VISIBILITY"), @android.view.ViewDebug.FlagToString(mask=0x8000, equals=0x8000, name="WILL_NOT_REPLACE_ON_RELAUNCH"), @android.view.ViewDebug.FlagToString(mask=0x10000, equals=0x10000, name="LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME"), @android.view.ViewDebug.FlagToString(mask=0x20000, equals=0x20000, name="FORCE_DRAW_STATUS_BAR_BACKGROUND"), @android.view.ViewDebug.FlagToString(mask=0x40000, equals=0x40000, name="SUSTAINED_PERFORMANCE_MODE"), @android.view.ViewDebug.FlagToString(mask=0x80000, equals=0x80000, name="HIDE_NON_SYSTEM_OVERLAY_WINDOWS"), @android.view.ViewDebug.FlagToString(mask=0x100000, equals=0x100000, name="IS_ROUNDED_CORNERS_OVERLAY"), @android.view.ViewDebug.FlagToString(mask=0x400000, equals=0x400000, name="IS_SCREEN_DECOR"), @android.view.ViewDebug.FlagToString(mask=0x800000, equals=0x800000, name="STATUS_FORCE_SHOW_NAVIGATION"), @android.view.ViewDebug.FlagToString(mask=0x1000000, equals=0x1000000, name="COLOR_SPACE_AGNOSTIC"), @android.view.ViewDebug.FlagToString(mask=0x4000000, equals=0x4000000, name="APPEARANCE_CONTROLLED"), @android.view.ViewDebug.FlagToString(mask=0x8000000, equals=0x8000000, name="BEHAVIOR_CONTROLLED"), @android.view.ViewDebug.FlagToString(mask=0x10000000, equals=0x10000000, name="FIT_INSETS_CONTROLLED"), @android.view.ViewDebug.FlagToString(mask=0x20000000, equals=0x20000000, name="ONLY_DRAW_BOTTOM_BAR_BACKGROUND")}) public int privateFlags; + field @android.view.ViewDebug.ExportedProperty(flagMapping={@android.view.ViewDebug.FlagToString(mask=0x1, equals=0x1, name="FAKE_HARDWARE_ACCELERATED"), @android.view.ViewDebug.FlagToString(mask=0x2, equals=0x2, name="FORCE_HARDWARE_ACCELERATED"), @android.view.ViewDebug.FlagToString(mask=0x4, equals=0x4, name="WANTS_OFFSET_NOTIFICATIONS"), @android.view.ViewDebug.FlagToString(mask=0x10, equals=0x10, name="SHOW_FOR_ALL_USERS"), @android.view.ViewDebug.FlagToString(mask=android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION, equals=android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION, name="NO_MOVE_ANIMATION"), @android.view.ViewDebug.FlagToString(mask=0x80, equals=0x80, name="COMPATIBLE_WINDOW"), @android.view.ViewDebug.FlagToString(mask=0x100, equals=0x100, name="SYSTEM_ERROR"), @android.view.ViewDebug.FlagToString(mask=0x800, equals=0x800, name="DISABLE_WALLPAPER_TOUCH_EVENTS"), @android.view.ViewDebug.FlagToString(mask=0x1000, equals=0x1000, name="FORCE_STATUS_BAR_VISIBLE"), @android.view.ViewDebug.FlagToString(mask=0x2000, equals=0x2000, name="PRESERVE_GEOMETRY"), @android.view.ViewDebug.FlagToString(mask=0x4000, equals=0x4000, name="FORCE_DECOR_VIEW_VISIBILITY"), @android.view.ViewDebug.FlagToString(mask=0x8000, equals=0x8000, name="WILL_NOT_REPLACE_ON_RELAUNCH"), @android.view.ViewDebug.FlagToString(mask=0x10000, equals=0x10000, name="LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME"), @android.view.ViewDebug.FlagToString(mask=0x20000, equals=0x20000, name="FORCE_DRAW_STATUS_BAR_BACKGROUND"), @android.view.ViewDebug.FlagToString(mask=0x40000, equals=0x40000, name="SUSTAINED_PERFORMANCE_MODE"), @android.view.ViewDebug.FlagToString(mask=0x80000, equals=0x80000, name="HIDE_NON_SYSTEM_OVERLAY_WINDOWS"), @android.view.ViewDebug.FlagToString(mask=0x100000, equals=0x100000, name="IS_ROUNDED_CORNERS_OVERLAY"), @android.view.ViewDebug.FlagToString(mask=0x400000, equals=0x400000, name="IS_SCREEN_DECOR"), @android.view.ViewDebug.FlagToString(mask=0x800000, equals=0x800000, name="STATUS_FORCE_SHOW_NAVIGATION"), @android.view.ViewDebug.FlagToString(mask=0x1000000, equals=0x1000000, name="COLOR_SPACE_AGNOSTIC"), @android.view.ViewDebug.FlagToString(mask=0x4000000, equals=0x4000000, name="APPEARANCE_CONTROLLED"), @android.view.ViewDebug.FlagToString(mask=0x8000000, equals=0x8000000, name="BEHAVIOR_CONTROLLED"), @android.view.ViewDebug.FlagToString(mask=0x10000000, equals=0x10000000, name="FIT_INSETS_CONTROLLED")}) public int privateFlags; } } diff --git a/api/test-lint-baseline.txt b/api/test-lint-baseline.txt index 603f7a259462..54f7f68d96cb 100644 --- a/api/test-lint-baseline.txt +++ b/api/test-lint-baseline.txt @@ -7,6 +7,16 @@ AcronymName: android.app.NotificationChannel#setImportanceLockedByOEM(boolean): ActionValue: android.location.Location#EXTRA_NO_GPS_LOCATION: +// Tethering broadcast action / extras cannot change name for backwards compatibility +ActionValue: android.net.TetheringManager#ACTION_TETHER_STATE_CHANGED: + Inconsistent action value; expected `android.net.action.TETHER_STATE_CHANGED`, was `android.net.conn.TETHER_STATE_CHANGED` +ActionValue: android.net.TetheringManager#EXTRA_ACTIVE_TETHER: + Inconsistent extra value; expected `android.net.extra.ACTIVE_TETHER`, was `tetherArray` +ActionValue: android.net.TetheringManager#EXTRA_AVAILABLE_TETHER: + Inconsistent extra value; expected `android.net.extra.AVAILABLE_TETHER`, was `availableArray` +ActionValue: android.net.TetheringManager#EXTRA_ERRORED_TETHER: + Inconsistent extra value; expected `android.net.extra.ERRORED_TETHER`, was `erroredArray` + ActionValue: android.telephony.ims.ImsCallProfile#EXTRA_ADDITIONAL_CALL_INFO: ActionValue: android.telephony.ims.ImsCallProfile#EXTRA_CALL_RAT_TYPE: diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 468a43fe84f9..d05ac189c834 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -810,6 +810,7 @@ message ScheduledJobStateChanged { FREQUENT = 2; RARE = 3; NEVER = 4; + RESTRICTED = 5; } optional Bucket standby_bucket = 5 [default = UNKNOWN]; @@ -3303,6 +3304,9 @@ message UiEventReported { // For example, the package posting a notification, or the destination package of a share. optional int32 uid = 2 [(is_uid) = true]; optional string package_name = 3; + // An identifier used to disambiguate which logs refer to a particular instance of some + // UI element. Useful when there might be multiple instances simultaneously active. + optional int32 instance_id = 4; } /** diff --git a/cmds/svc/src/com/android/commands/svc/UsbCommand.java b/cmds/svc/src/com/android/commands/svc/UsbCommand.java index 3893be49e739..cd751f4e2d20 100644 --- a/cmds/svc/src/com/android/commands/svc/UsbCommand.java +++ b/cmds/svc/src/com/android/commands/svc/UsbCommand.java @@ -42,7 +42,9 @@ public class UsbCommand extends Svc.Command { + " Sets the functions which, if the device was charging, become current on" + "screen unlock. If function is blank, turn off this feature.\n" + " svc usb getFunctions\n" - + " Gets the list of currently enabled functions\n\n" + + " Gets the list of currently enabled functions\n" + + " svc usb resetUsbGadget\n" + + " Reset usb gadget\n\n" + "possible values of [function] are any of 'mtp', 'ptp', 'rndis', 'midi'\n"; } @@ -75,6 +77,13 @@ public class UsbCommand extends Svc.Command { System.err.println("Error communicating with UsbManager: " + e); } return; + } else if ("resetUsbGadget".equals(args[1])) { + try { + usbMgr.resetUsbGadget(); + } catch (RemoteException e) { + System.err.println("Error communicating with UsbManager: " + e); + } + return; } } System.err.println(longHelp()); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index b82a67556fc0..2ca5b1d5c76f 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -7326,6 +7326,15 @@ public final class ActivityThread extends ClientTransactionHandler { } } + float getFloatCoreSetting(String key, float defaultValue) { + synchronized (mResourcesManager) { + if (mCoreSettings != null) { + return mCoreSettings.getFloat(key, defaultValue); + } + return defaultValue; + } + } + private static class AndroidOs extends ForwardingOs { /** * Install selective syscall interception. For example, this is used to diff --git a/core/java/android/app/AppGlobals.java b/core/java/android/app/AppGlobals.java index 81e1565ee86c..f66bf0d89c37 100644 --- a/core/java/android/app/AppGlobals.java +++ b/core/java/android/app/AppGlobals.java @@ -75,4 +75,20 @@ public class AppGlobals { return defaultValue; } } + + /** + * Gets the value of a float core setting. + * + * @param key The setting key. + * @param defaultValue The setting default value. + * @return The core settings. + */ + public static float getFloatCoreSetting(String key, float defaultValue) { + ActivityThread currentActivityThread = ActivityThread.currentActivityThread(); + if (currentActivityThread != null) { + return currentActivityThread.getFloatCoreSetting(key, defaultValue); + } else { + return defaultValue; + } + } } diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index bc7e1e591021..46f86690a753 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1159,6 +1159,7 @@ public class AppOpsManager { @SystemApi public static final String OPSTR_ACCESS_ACCESSIBILITY = "android:access_accessibility"; /** @hide Read device identifiers */ + @SystemApi public static final String OPSTR_READ_DEVICE_IDENTIFIERS = "android:read_device_identifiers"; /** @hide Query all packages on device */ public static final String OPSTR_QUERY_ALL_PACKAGES = "android:query_all_packages"; diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index fd56834ac361..4e6319db97f4 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -868,7 +868,7 @@ public class ApplicationPackageManager extends PackageManager { } @Override - public CharSequence getBackgroundPermissionButtonLabel() { + public CharSequence getBackgroundPermissionOptionLabel() { try { String permissionController = getPermissionControllerPackageName(); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index cd84310356b1..b7555ee1c04e 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -19,7 +19,6 @@ package android.app; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.AutofillOptions; import android.content.BroadcastReceiver; @@ -201,7 +200,7 @@ class ContextImpl extends Context { @UnsupportedAppUsage private @Nullable ClassLoader mClassLoader; - private final @Nullable IBinder mActivityToken; + private final @Nullable IBinder mToken; private final @NonNull UserHandle mUser; @@ -219,7 +218,7 @@ class ContextImpl extends Context { private final @NonNull ResourcesManager mResourcesManager; @UnsupportedAppUsage private @NonNull Resources mResources; - private @Nullable Display mDisplay; // may be null if default display + private @Nullable Display mDisplay; // may be null if invalid display or not initialized yet. @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private final int mFlags; @@ -244,6 +243,9 @@ class ContextImpl extends Context { private final Object mSync = new Object(); + private boolean mIsSystemOrSystemUiContext; + private boolean mIsUiContext; + @GuardedBy("mSync") private File mDatabasesDir; @GuardedBy("mSync") @@ -1883,6 +1885,9 @@ class ContextImpl extends Context { @Override public Object getSystemService(String name) { + if (isUiComponent(name) && !isUiContext()) { + Log.w(TAG, name + " should be accessed from Activity or other visual Context"); + } return SystemServiceRegistry.getSystemService(this, name); } @@ -1891,6 +1896,15 @@ class ContextImpl extends Context { return SystemServiceRegistry.getSystemServiceName(serviceClass); } + boolean isUiContext() { + return mIsSystemOrSystemUiContext || mIsUiContext; + } + + private static boolean isUiComponent(String name) { + return WINDOW_SERVICE.equals(name) || LAYOUT_INFLATER_SERVICE.equals(name) + || WALLPAPER_SERVICE.equals(name); + } + @Override public int checkPermission(String permission, int pid, int uid) { if (permission == null) { @@ -2229,12 +2243,12 @@ class ContextImpl extends Context { LoadedApk pi = mMainThread.getPackageInfo(application, mResources.getCompatibilityInfo(), flags | CONTEXT_REGISTER_PACKAGE); if (pi != null) { - ContextImpl c = new ContextImpl(this, mMainThread, pi, null, null, mActivityToken, + ContextImpl c = new ContextImpl(this, mMainThread, pi, null, null, mToken, new UserHandle(UserHandle.getUserId(application.uid)), flags, null, null); final int displayId = getDisplayId(); - c.setResources(createResources(mActivityToken, pi, null, displayId, null, + c.setResources(createResources(mToken, pi, null, displayId, null, getDisplayAdjustments(displayId).getCompatibilityInfo())); if (c.mResources != null) { return c; @@ -2258,18 +2272,18 @@ class ContextImpl extends Context { // The system resources are loaded in every application, so we can safely copy // the context without reloading Resources. return new ContextImpl(this, mMainThread, mPackageInfo, mFeatureId, null, - mActivityToken, user, flags, null, null); + mToken, user, flags, null, null); } LoadedApk pi = mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(), flags | CONTEXT_REGISTER_PACKAGE, user.getIdentifier()); if (pi != null) { ContextImpl c = new ContextImpl(this, mMainThread, pi, mFeatureId, null, - mActivityToken, user, flags, null, null); + mToken, user, flags, null, null); final int displayId = getDisplayId(); - c.setResources(createResources(mActivityToken, pi, null, displayId, null, + c.setResources(createResources(mToken, pi, null, displayId, null, getDisplayAdjustments(displayId).getCompatibilityInfo())); if (c.mResources != null) { return c; @@ -2301,12 +2315,12 @@ class ContextImpl extends Context { final String[] paths = mPackageInfo.getSplitPaths(splitName); final ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, - mFeatureId, splitName, mActivityToken, mUser, mFlags, classLoader, null); + mFeatureId, splitName, mToken, mUser, mFlags, classLoader, null); final int displayId = getDisplayId(); context.setResources(ResourcesManager.getInstance().getResources( - mActivityToken, + mToken, mPackageInfo.getResDir(), paths, mPackageInfo.getOverlayDirs(), @@ -2325,10 +2339,10 @@ class ContextImpl extends Context { } ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mFeatureId, - mSplitName, mActivityToken, mUser, mFlags, mClassLoader, null); + mSplitName, mToken, mUser, mFlags, mClassLoader, null); final int displayId = getDisplayId(); - context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId, + context.setResources(createResources(mToken, mPackageInfo, mSplitName, displayId, overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo())); return context; } @@ -2340,19 +2354,36 @@ class ContextImpl extends Context { } ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mFeatureId, - mSplitName, mActivityToken, mUser, mFlags, mClassLoader, null); + mSplitName, mToken, mUser, mFlags, mClassLoader, null); final int displayId = display.getDisplayId(); - context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId, + context.setResources(createResources(mToken, mPackageInfo, mSplitName, displayId, null, getDisplayAdjustments(displayId).getCompatibilityInfo())); context.mDisplay = display; return context; } @Override + public @NonNull WindowContext createWindowContext(int type) { + if (getDisplay() == null) { + throw new UnsupportedOperationException("WindowContext can only be created from " + + "other visual contexts, such as Activity or one created with " + + "Context#createDisplayContext(Display)"); + } + return new WindowContext(this, null /* token */, type); + } + + ContextImpl createBaseWindowContext(IBinder token) { + ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mFeatureId, + mSplitName, token, mUser, mFlags, mClassLoader, null); + context.mIsUiContext = true; + return context; + } + + @Override public @NonNull Context createFeatureContext(@Nullable String featureId) { return new ContextImpl(this, mMainThread, mPackageInfo, featureId, mSplitName, - mActivityToken, mUser, mFlags, mClassLoader, null); + mToken, mUser, mFlags, mClassLoader, null); } @Override @@ -2360,7 +2391,7 @@ class ContextImpl extends Context { final int flags = (mFlags & ~Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE) | Context.CONTEXT_DEVICE_PROTECTED_STORAGE; return new ContextImpl(this, mMainThread, mPackageInfo, mFeatureId, mSplitName, - mActivityToken, mUser, flags, mClassLoader, null); + mToken, mUser, flags, mClassLoader, null); } @Override @@ -2368,7 +2399,7 @@ class ContextImpl extends Context { final int flags = (mFlags & ~Context.CONTEXT_DEVICE_PROTECTED_STORAGE) | Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE; return new ContextImpl(this, mMainThread, mPackageInfo, mFeatureId, mSplitName, - mActivityToken, mUser, flags, mClassLoader, null); + mToken, mUser, flags, mClassLoader, null); } @Override @@ -2394,8 +2425,6 @@ class ContextImpl extends Context { return (mFlags & Context.CONTEXT_IGNORE_SECURITY) != 0; } - @UnsupportedAppUsage - @TestApi @Override public Display getDisplay() { if (mDisplay == null) { @@ -2408,7 +2437,8 @@ class ContextImpl extends Context { @Override public int getDisplayId() { - return mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY; + final Display display = getDisplay(); + return display != null ? display.getDisplayId() : Display.DEFAULT_DISPLAY; } @Override @@ -2518,6 +2548,7 @@ class ContextImpl extends Context { context.setResources(packageInfo.getResources()); context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(), context.mResourcesManager.getDisplayMetrics()); + context.mIsSystemOrSystemUiContext = true; return context; } @@ -2535,6 +2566,7 @@ class ContextImpl extends Context { context.setResources(createResources(null, packageInfo, null, displayId, null, packageInfo.getCompatibilityInfo())); context.updateDisplay(displayId); + context.mIsSystemOrSystemUiContext = true; return context; } @@ -2584,6 +2616,7 @@ class ContextImpl extends Context { ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, activityInfo.splitName, activityToken, null, 0, classLoader, null); + context.mIsUiContext = true; // Clamp display ID to DEFAULT_DISPLAY if it is INVALID_DISPLAY. displayId = (displayId != Display.INVALID_DISPLAY) ? displayId : Display.DEFAULT_DISPLAY; @@ -2629,7 +2662,7 @@ class ContextImpl extends Context { } mMainThread = mainThread; - mActivityToken = activityToken; + mToken = activityToken; mFlags = flags; if (user == null) { @@ -2649,6 +2682,7 @@ class ContextImpl extends Context { opPackageName = container.mOpPackageName; setResources(container.mResources); mDisplay = container.mDisplay; + mIsSystemOrSystemUiContext = container.mIsSystemOrSystemUiContext; } else { mBasePackageName = packageInfo.mPackageName; ApplicationInfo ainfo = packageInfo.getApplicationInfo(); @@ -2710,7 +2744,7 @@ class ContextImpl extends Context { @Override @UnsupportedAppUsage public IBinder getActivityToken() { - return mActivityToken; + return mToken; } private void checkMode(int mode) { diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl index 4cb8d936aa9c..34684c4c4228 100644 --- a/core/java/android/app/IWallpaperManager.aidl +++ b/core/java/android/app/IWallpaperManager.aidl @@ -175,4 +175,11 @@ interface IWallpaperManager { * Called from SystemUI when it shows the AoD UI. */ oneway void setInAmbientMode(boolean inAmbientMode, long animationDuration); + + /** + * Called when the wallpaper needs to zoom out. + * The zoom value goes from 0 to 1 (inclusive) where 1 means fully zoomed out, + * 0 means fully zoomed in + */ + oneway void setWallpaperZoomOut(float zoom, String callingPackage, int displayId); } diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 0c5e67cd4c6a..f0d0e98b841f 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -1180,13 +1180,26 @@ public final class LoadedApk { } try { - java.lang.ClassLoader cl = getClassLoader(); + final java.lang.ClassLoader cl = getClassLoader(); if (!mPackageName.equals("android")) { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "initializeJavaContextClassLoader"); initializeJavaContextClassLoader(); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } + + // Rewrite the R 'constants' for all library apks. + SparseArray<String> packageIdentifiers = getAssets().getAssignedPackageIdentifiers( + false, false); + for (int i = 0, n = packageIdentifiers.size(); i < n; i++) { + final int id = packageIdentifiers.keyAt(i); + if (id == 0x01 || id == 0x7f) { + continue; + } + + rewriteRValues(cl, packageIdentifiers.valueAt(i), id); + } + ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this); app = mActivityThread.mInstrumentation.newApplication( cl, appClass, appContext); @@ -1215,19 +1228,6 @@ public final class LoadedApk { } } - // Rewrite the R 'constants' for all library apks. - SparseArray<String> packageIdentifiers = getAssets().getAssignedPackageIdentifiers( - false, false); - final int N = packageIdentifiers.size(); - for (int i = 0; i < N; i++) { - final int id = packageIdentifiers.keyAt(i); - if (id == 0x01 || id == 0x7f) { - continue; - } - - rewriteRValues(getClassLoader(), packageIdentifiers.valueAt(i), id); - } - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); return app; diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 3c4e861d55f8..1af275fedd74 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1004,6 +1004,31 @@ public class Notification implements Parcelable */ public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory"; + + /** + * {@link #extras} key: this is a remote input history which can include media messages + * in addition to text, as supplied to + * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} or + * {@link Builder#setRemoteInputHistory(CharSequence[])}. + * + * SystemUI can populate this through + * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} with the most recent inputs + * that have been sent through a {@link RemoteInput} of this Notification. These items can + * represent either media content (specified by a URI and a MIME type) or a text message + * (described by a CharSequence). + * + * To maintain compatibility, this can also be set by apps with + * {@link Builder#setRemoteInputHistory(CharSequence[])}, which will create a + * {@link RemoteInputHistoryItem} for each of the provided text-only messages. + * + * The extra with this key is of type {@link RemoteInputHistoryItem[]} and contains the most + * recent entry at the 0 index, the second most recent at the 1 index, etc. + * + * @see Builder#setRemoteInputHistory(RemoteInputHistoryItem[]) + * @hide + */ + public static final String EXTRA_REMOTE_INPUT_HISTORY_ITEMS = "android.remoteInputHistoryItems"; + /** * {@link #extras} key: boolean as supplied to * {@link Builder#setShowRemoteInputSpinner(boolean)}. @@ -3833,12 +3858,37 @@ public class Notification implements Parcelable if (text == null) { mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, null); } else { - final int N = Math.min(MAX_REPLY_HISTORY, text.length); - CharSequence[] safe = new CharSequence[N]; - for (int i = 0; i < N; i++) { + final int itemCount = Math.min(MAX_REPLY_HISTORY, text.length); + CharSequence[] safe = new CharSequence[itemCount]; + RemoteInputHistoryItem[] items = new RemoteInputHistoryItem[itemCount]; + for (int i = 0; i < itemCount; i++) { safe[i] = safeCharSequence(text[i]); + items[i] = new RemoteInputHistoryItem(text[i]); } mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, safe); + + // Also add these messages as structured history items. + mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, items); + } + return this; + } + + /** + * Set the remote input history, with support for embedding URIs and mime types for + * images and other media. + * @hide + */ + @NonNull + public Builder setRemoteInputHistory(RemoteInputHistoryItem[] items) { + if (items == null) { + mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, null); + } else { + final int itemCount = Math.min(MAX_REPLY_HISTORY, items.length); + RemoteInputHistoryItem[] history = new RemoteInputHistoryItem[itemCount]; + for (int i = 0; i < itemCount; i++) { + history[i] = items[i]; + } + mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, history); } return this; } @@ -5246,16 +5296,17 @@ public class Notification implements Parcelable big.setViewVisibility(R.id.actions_container, View.GONE); } - CharSequence[] replyText = mN.extras.getCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY); - if (validRemoteInput && replyText != null - && replyText.length > 0 && !TextUtils.isEmpty(replyText[0]) + RemoteInputHistoryItem[] replyText = (RemoteInputHistoryItem[]) + mN.extras.getParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS); + if (validRemoteInput && replyText != null && replyText.length > 0 + && !TextUtils.isEmpty(replyText[0].getText()) && p.maxRemoteInputHistory > 0) { boolean showSpinner = mN.extras.getBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER); big.setViewVisibility(R.id.notification_material_reply_container, View.VISIBLE); big.setViewVisibility(R.id.notification_material_reply_text_1_container, View.VISIBLE); big.setTextViewText(R.id.notification_material_reply_text_1, - processTextSpans(replyText[0])); + processTextSpans(replyText[0].getText())); setTextViewColorSecondary(big, R.id.notification_material_reply_text_1, p); big.setViewVisibility(R.id.notification_material_reply_progress, showSpinner ? View.VISIBLE : View.GONE); @@ -5264,19 +5315,19 @@ public class Notification implements Parcelable ColorStateList.valueOf( isColorized(p) ? getPrimaryTextColor(p) : resolveContrastColor(p))); - if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1]) + if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1].getText()) && p.maxRemoteInputHistory > 1) { big.setViewVisibility(R.id.notification_material_reply_text_2, View.VISIBLE); big.setTextViewText(R.id.notification_material_reply_text_2, - processTextSpans(replyText[1])); + processTextSpans(replyText[1].getText())); setTextViewColorSecondary(big, R.id.notification_material_reply_text_2, p); - if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2]) + if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2].getText()) && p.maxRemoteInputHistory > 2) { big.setViewVisibility( R.id.notification_material_reply_text_3, View.VISIBLE); big.setTextViewText(R.id.notification_material_reply_text_3, - processTextSpans(replyText[2])); + processTextSpans(replyText[2].getText())); setTextViewColorSecondary(big, R.id.notification_material_reply_text_3, p); } } @@ -7517,7 +7568,7 @@ public class Notification implements Parcelable @Nullable private final Person mSender; /** True if this message was generated from the extra - * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY} + * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS} */ private final boolean mRemoteInputHistory; @@ -7569,7 +7620,7 @@ public class Notification implements Parcelable * Should be <code>null</code> for messages by the current user, in which case * the platform will insert the user set in {@code MessagingStyle(Person)}. * @param remoteInputHistory True if the messages was generated from the extra - * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY}. + * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}. * <p> * The person provided should contain an Icon, set with * {@link Person.Builder#setIcon(Icon)} and also have a name provided @@ -7676,7 +7727,7 @@ public class Notification implements Parcelable /** * @return True if the message was generated from - * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY}. + * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}. * @hide */ public boolean isRemoteInputHistory() { @@ -7906,8 +7957,8 @@ public class Notification implements Parcelable if (mBuilder.mActions.size() > 0) { maxRows--; } - CharSequence[] remoteInputHistory = mBuilder.mN.extras.getCharSequenceArray( - EXTRA_REMOTE_INPUT_HISTORY); + RemoteInputHistoryItem[] remoteInputHistory = (RemoteInputHistoryItem[]) + mBuilder.mN.extras.getParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS); if (remoteInputHistory != null && remoteInputHistory.length > NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION) { // Let's remove some messages to make room for the remote input history. diff --git a/core/java/android/app/RemoteInputHistoryItem.java b/core/java/android/app/RemoteInputHistoryItem.java new file mode 100644 index 000000000000..091db3f142ae --- /dev/null +++ b/core/java/android/app/RemoteInputHistoryItem.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Stores historical input from a RemoteInput attached to a Notification. + * + * History items represent either a text message (specified by providing a CharSequence, + * or a media message (specified by providing a URI and a MIME type). Media messages must also + * include text to insert when the image cannot be loaded, ex. when URI read permission has not been + * granted correctly. + * + * @hide + */ +public class RemoteInputHistoryItem implements Parcelable { + private CharSequence mText; + private String mMimeType; + private Uri mUri; + + public RemoteInputHistoryItem(String mimeType, Uri uri, CharSequence backupText) { + this.mMimeType = mimeType; + this.mUri = uri; + this.mText = Notification.safeCharSequence(backupText); + } + + public RemoteInputHistoryItem(CharSequence text) { + this.mText = Notification.safeCharSequence(text); + } + + protected RemoteInputHistoryItem(Parcel in) { + mText = in.readCharSequence(); + mMimeType = in.readStringNoHelper(); + mUri = in.readParcelable(Uri.class.getClassLoader()); + } + + public static final Creator<RemoteInputHistoryItem> CREATOR = + new Creator<RemoteInputHistoryItem>() { + @Override + public RemoteInputHistoryItem createFromParcel(Parcel in) { + return new RemoteInputHistoryItem(in); + } + + @Override + public RemoteInputHistoryItem[] newArray(int size) { + return new RemoteInputHistoryItem[size]; + } + }; + + public CharSequence getText() { + return mText; + } + + public String getMimeType() { + return mMimeType; + } + + public Uri getUri() { + return mUri; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeCharSequence(mText); + dest.writeStringNoHelper(mMimeType); + dest.writeParcelable(mUri, flags); + } +} diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index c1e535643ddf..3abd5094e11d 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -106,6 +106,7 @@ import android.media.session.MediaSessionManager; import android.media.soundtrigger.SoundTriggerManager; import android.media.tv.ITvInputManager; import android.media.tv.TvInputManager; +import android.net.ConnectivityDiagnosticsManager; import android.net.ConnectivityManager; import android.net.ConnectivityThread; import android.net.EthernetManager; @@ -376,6 +377,18 @@ public final class SystemServiceRegistry { return new IpSecManager(ctx, service); }}); + registerService(Context.CONNECTIVITY_DIAGNOSTICS_SERVICE, + ConnectivityDiagnosticsManager.class, + new CachedServiceFetcher<ConnectivityDiagnosticsManager>() { + @Override + public ConnectivityDiagnosticsManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + // ConnectivityDiagnosticsManager is backed by ConnectivityService + IBinder b = ServiceManager.getServiceOrThrow(Context.CONNECTIVITY_SERVICE); + IConnectivityManager service = IConnectivityManager.Stub.asInterface(b); + return new ConnectivityDiagnosticsManager(ctx, service); + }}); + registerService( Context.TEST_NETWORK_SERVICE, TestNetworkManager.class, diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index a88500920e97..6f1effd228e3 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -1848,6 +1848,24 @@ public class WallpaperManager { } /** + * Set the current zoom out level of the wallpaper + * @param zoom from 0 to 1 (inclusive) where 1 means fully zoomed out, 0 means fully zoomed in + * + * @hide + */ + public void setWallpaperZoomOut(float zoom) { + if (zoom < 0 || zoom > 1f) { + throw new IllegalArgumentException("zoom must be between 0 and one: " + zoom); + } + try { + sGlobals.mService.setWallpaperZoomOut(zoom, mContext.getOpPackageName(), + mContext.getDisplayId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Returns whether wallpapers are supported for the calling user. If this function returns * {@code false}, any attempts to changing the wallpaper will have no effect, * and any attempt to obtain of the wallpaper will return {@code null}. diff --git a/core/java/android/app/WindowContext.java b/core/java/android/app/WindowContext.java new file mode 100644 index 000000000000..22cc14bd5ed6 --- /dev/null +++ b/core/java/android/app/WindowContext.java @@ -0,0 +1,123 @@ +/* + * 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; + +import android.annotation.NonNull; +import android.content.Context; +import android.content.ContextWrapper; +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.view.IWindowManager; +import android.view.WindowManagerGlobal; +import android.view.WindowManagerImpl; + +/** + * {@link WindowContext} is a context for non-activity windows such as + * {@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY} windows or system + * windows. Its resources and configuration are adjusted to the area of the display that will be + * used when a new window is added via {@link android.view.WindowManager.addView}. + * + * @see Context#createWindowContext(int) + * @hide + */ +// TODO(b/128338354): Handle config/display changes from server side. +public class WindowContext extends ContextWrapper { + private final WindowManagerImpl mWindowManager; + private final IWindowManager mWms; + private final IBinder mToken; + private final int mDisplayId; + private boolean mOwnsToken; + + /** + * Default constructor. Can either accept an existing token or generate one and registers it + * with the server if necessary. + * + * @param base Base {@link Context} for this new instance. + * @param token A valid {@link com.android.server.wm.WindowToken}. Pass {@code null} to generate + * one. + * @param type Window type to be used with this context. + * @hide + */ + public WindowContext(Context base, IBinder token, int type) { + super(null /* base */); + + mWms = WindowManagerGlobal.getWindowManagerService(); + if (token != null && !isWindowToken(token)) { + throw new IllegalArgumentException("Token must be registered to server."); + } + + final ContextImpl contextImpl = createBaseWindowContext(base, token); + attachBaseContext(contextImpl); + contextImpl.setOuterContext(this); + + mToken = token != null ? token : new Binder(); + mDisplayId = getDisplayId(); + mWindowManager = new WindowManagerImpl(this); + mWindowManager.setDefaultToken(mToken); + + // TODO(b/128338354): Obtain the correct config from WM and adjust resources. + if (token != null) { + mOwnsToken = false; + return; + } + try { + mWms.addWindowContextToken(mToken, type, mDisplayId, getPackageName()); + // TODO(window-context): remove token with a DeathObserver + } catch (RemoteException e) { + mOwnsToken = false; + throw e.rethrowFromSystemServer(); + } + mOwnsToken = true; + } + + /** Check if the passed window token is registered with the server. */ + private boolean isWindowToken(@NonNull IBinder token) { + try { + return mWms.isWindowToken(token); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + return false; + } + + private static ContextImpl createBaseWindowContext(Context outer, IBinder token) { + final ContextImpl contextImpl = ContextImpl.getImpl(outer); + return contextImpl.createBaseWindowContext(token); + } + + @Override + public Object getSystemService(String name) { + if (WINDOW_SERVICE.equals(name)) { + return mWindowManager; + } + return super.getSystemService(name); + } + + @Override + protected void finalize() throws Throwable { + if (mOwnsToken) { + try { + mWms.removeWindowToken(mToken, mDisplayId); + mOwnsToken = false; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + super.finalize(); + } +} diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index ffa00472006f..aeeabb7c1726 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -4232,7 +4232,18 @@ public class DevicePolicyManager { * device by first calling {@link #resetPassword} to set the password and then lock the device. * <p> * This method can be called on the {@link DevicePolicyManager} instance returned by - * {@link #getParentProfileInstance(ComponentName)} in order to lock the parent profile. + * {@link #getParentProfileInstance(ComponentName)} in order to lock the parent profile as + * well as the managed profile. + * <p> + * NOTE: In order to lock the parent profile and evict the encryption key of the managed + * profile, {@link #lockNow()} must be called twice: First, {@link #lockNow()} should be called + * on the {@link DevicePolicyManager} instance returned by + * {@link #getParentProfileInstance(ComponentName)}, then {@link #lockNow(int)} should be + * called on the {@link DevicePolicyManager} instance associated with the managed profile, + * with the {@link #FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY} flag. + * Calling the method twice in this order ensures that all users are locked and does not + * stop the device admin on the managed profile from issuing a second call to lock its own + * profile. * * @param flags May be 0 or {@link #FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY}. * @throws SecurityException if the calling application does not own an active administrator @@ -9357,6 +9368,16 @@ public class DevicePolicyManager { * {@link android.os.Build.VERSION_CODES#M} the app-op matching the permission is set to * {@link android.app.AppOpsManager#MODE_IGNORED}, but the permission stays granted. * + * NOTE: Starting from Android R, location-related permissions cannot be granted by the + * admin: Calling this method with {@link #PERMISSION_GRANT_STATE_GRANTED} for any of the + * following permissions will return false: + * + * <ul> + * <li>{@code ACCESS_FINE_LOCATION}</li> + * <li>{@code ACCESS_BACKGROUND_LOCATION}</li> + * <li>{@code ACCESS_COARSE_LOCATION}</li> + * </ul> + * * @param admin Which profile or device owner this request is associated with. * @param packageName The application to grant or revoke a permission to. * @param permission The permission to grant or revoke. @@ -11433,6 +11454,10 @@ public class DevicePolicyManager { * * <p>Previous calls are overridden by each subsequent call to this method. * + * <p>When previously-set cross-profile packages are missing from {@code packageNames}, the + * app-op for {@code INTERACT_ACROSS_PROFILES} will be reset for those packages. This will not + * occur for packages that are whitelisted by the OEM. + * * @param admin the {@link DeviceAdminReceiver} this request is associated with * @param packageNames the new cross-profile package names */ diff --git a/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java b/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java index 1e6ab4136187..bea1bd6e70d6 100644 --- a/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java +++ b/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.UserIdInt; +import android.graphics.Bitmap; import android.os.Binder; import android.os.Bundle; import android.os.RemoteException; @@ -77,6 +78,28 @@ public final class ContentSuggestionsManager { } /** + * Hints to the system that a new context image using the provided bitmap should be sent to + * the system content suggestions service. + * + * @param bitmap the new context image + * @param imageContextRequestExtras sent with request to provide implementation specific + * extra information. + */ + public void provideContextImage( + @NonNull Bitmap bitmap, @NonNull Bundle imageContextRequestExtras) { + if (mService == null) { + Log.e(TAG, "provideContextImage called, but no ContentSuggestionsManager configured"); + return; + } + + try { + mService.provideContextBitmap(mUser, bitmap, imageContextRequestExtras); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** * Hints to the system that a new context image for the provided task should be sent to the * system content suggestions service. * diff --git a/core/java/android/app/contentsuggestions/IContentSuggestionsManager.aidl b/core/java/android/app/contentsuggestions/IContentSuggestionsManager.aidl index b18a758ff69e..8e6338babf22 100644 --- a/core/java/android/app/contentsuggestions/IContentSuggestionsManager.aidl +++ b/core/java/android/app/contentsuggestions/IContentSuggestionsManager.aidl @@ -20,6 +20,7 @@ import android.app.contentsuggestions.IClassificationsCallback; import android.app.contentsuggestions.ISelectionsCallback; import android.app.contentsuggestions.ClassificationsRequest; import android.app.contentsuggestions.SelectionsRequest; +import android.graphics.Bitmap; import android.os.Bundle; import android.os.UserHandle; import com.android.internal.os.IResultReceiver; @@ -30,6 +31,10 @@ oneway interface IContentSuggestionsManager { int userId, int taskId, in Bundle imageContextRequestExtras); + void provideContextBitmap( + int userId, + in Bitmap bitmap, + in Bundle imageContextRequestExtras); void suggestContentSelections( int userId, in SelectionsRequest request, diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java index a60e591dd0e6..5668944dfd4e 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -142,7 +142,7 @@ public final class UsageStatsManager { /** * The app has not be used for several days and/or is unlikely to be used for several days. - * Apps in this bucket will have the most restrictions, including network restrictions, except + * Apps in this bucket will have more restrictions, including network restrictions, except * during certain short periods (at a minimum, once a day) when they are allowed to execute * jobs, access the network, etc. * @see #getAppStandbyBucket() @@ -150,6 +150,15 @@ public final class UsageStatsManager { public static final int STANDBY_BUCKET_RARE = 40; /** + * The app has not be used for several days, is unlikely to be used for several days, and has + * been misbehaving in some manner. + * Apps in this bucket will have the most restrictions, including network restrictions and + * additional restrictions on jobs. + * @see #getAppStandbyBucket() + */ + public static final int STANDBY_BUCKET_RESTRICTED = 45; + + /** * The app has never been used. * {@hide} */ @@ -278,6 +287,26 @@ public final class UsageStatsManager { * @hide */ public static final int REASON_SUB_PREDICTED_RESTORED = 0x0001; + /** + * The reason for restricting the app is unknown or undefined. + * @hide + */ + public static final int REASON_SUB_RESTRICT_UNDEFINED = 0x0000; + /** + * The app was unnecessarily using system resources (battery, memory, etc) in the background. + * @hide + */ + public static final int REASON_SUB_RESTRICT_BACKGROUND_RESOURCE_USAGE = 0x0001; + /** + * The app was deemed to be intentionally abusive. + * @hide + */ + public static final int REASON_SUB_RESTRICT_ABUSE = 0x0002; + /** + * The app was displaying buggy behavior. + * @hide + */ + public static final int REASON_SUB_RESTRICT_BUGGY = 0x0003; /** @hide */ @@ -287,6 +316,7 @@ public final class UsageStatsManager { STANDBY_BUCKET_WORKING_SET, STANDBY_BUCKET_FREQUENT, STANDBY_BUCKET_RARE, + STANDBY_BUCKET_RESTRICTED, STANDBY_BUCKET_NEVER, }) @Retention(RetentionPolicy.SOURCE) @@ -598,7 +628,7 @@ public final class UsageStatsManager { * state of the app based on app usage patterns. Standby buckets determine how much an app will * be restricted from running background tasks such as jobs and alarms. * <p>Restrictions increase progressively from {@link #STANDBY_BUCKET_ACTIVE} to - * {@link #STANDBY_BUCKET_RARE}, with {@link #STANDBY_BUCKET_ACTIVE} being the least + * {@link #STANDBY_BUCKET_RESTRICTED}, with {@link #STANDBY_BUCKET_ACTIVE} being the least * restrictive. The battery level of the device might also affect the restrictions. * <p>Apps in buckets ≤ {@link #STANDBY_BUCKET_ACTIVE} have no standby restrictions imposed. * Apps in buckets > {@link #STANDBY_BUCKET_FREQUENT} may have network access restricted when @@ -642,7 +672,8 @@ public final class UsageStatsManager { /** * {@hide} * Changes an app's standby bucket to the provided value. The caller can only set the standby - * bucket for a different app than itself. + * bucket for a different app than itself. The caller will not be able to change an app's + * standby bucket if that app is in the {@link #STANDBY_BUCKET_RESTRICTED} bucket. * @param packageName the package name of the app to set the bucket for. A SecurityException * will be thrown if the package name is that of the caller. * @param bucket the standby bucket to set it to, which should be one of STANDBY_BUCKET_*. @@ -688,7 +719,8 @@ public final class UsageStatsManager { /** * {@hide} * Changes the app standby bucket for multiple apps at once. The Map is keyed by the package - * name and the value is one of STANDBY_BUCKET_*. + * name and the value is one of STANDBY_BUCKET_*. The caller will not be able to change an + * app's standby bucket if that app is in the {@link #STANDBY_BUCKET_RESTRICTED} bucket. * @param appBuckets a map of package name to bucket value. */ @SystemApi @@ -1027,6 +1059,20 @@ public final class UsageStatsManager { break; case REASON_MAIN_FORCED_BY_SYSTEM: sb.append("s"); + switch (standbyReason & REASON_SUB_MASK) { + case REASON_SUB_RESTRICT_ABUSE: + sb.append("-ra"); + break; + case REASON_SUB_RESTRICT_BACKGROUND_RESOURCE_USAGE: + sb.append("-rbru"); + break; + case REASON_SUB_RESTRICT_BUGGY: + sb.append("-rb"); + break; + case REASON_SUB_RESTRICT_UNDEFINED: + sb.append("-r"); + break; + } break; case REASON_MAIN_FORCED_BY_USER: sb.append("f"); diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index cb1f05573d93..e7513544a886 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -1222,6 +1222,7 @@ public final class BluetoothAdapter { if (mService != null) { return mService.factoryReset(); } + Log.e(TAG, "factoryReset(): IBluetooth Service is null"); SystemProperties.set("persist.bluetooth.factoryreset", "true"); } catch (RemoteException e) { Log.e(TAG, "", e); @@ -1239,7 +1240,7 @@ public final class BluetoothAdapter { */ @UnsupportedAppUsage @RequiresPermission(Manifest.permission.BLUETOOTH) - public @NonNull ParcelUuid[] getUuids() { + public @Nullable ParcelUuid[] getUuids() { if (getState() != STATE_ON) { return null; } diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java index 024bb06098ab..ec63fd058b16 100644 --- a/core/java/android/bluetooth/BluetoothPan.java +++ b/core/java/android/bluetooth/BluetoothPan.java @@ -30,6 +30,7 @@ import android.content.Context; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; +import android.util.CloseGuard; import android.util.Log; import java.lang.annotation.Retention; @@ -50,10 +51,11 @@ import java.util.List; * @hide */ @SystemApi -public final class BluetoothPan implements BluetoothProfile { +public final class BluetoothPan implements BluetoothProfile, AutoCloseable { private static final String TAG = "BluetoothPan"; private static final boolean DBG = true; private static final boolean VDBG = false; + private CloseGuard mCloseGuard; /** * Intent used to broadcast the change in connection state of the Pan @@ -166,10 +168,15 @@ public final class BluetoothPan implements BluetoothProfile { mAdapter = BluetoothAdapter.getDefaultAdapter(); mContext = context; mProfileConnector.connect(context, listener); + mCloseGuard = new CloseGuard(); + mCloseGuard.open("close"); } - @UnsupportedAppUsage - /*package*/ void close() { + /** + * Closes the connection to the service and unregisters callbacks + */ + @RequiresPermission(Manifest.permission.BLUETOOTH) + public void close() { if (VDBG) log("close()"); mProfileConnector.disconnect(); } @@ -178,8 +185,11 @@ public final class BluetoothPan implements BluetoothProfile { return mProfileConnector.getService(); } - + @RequiresPermission(Manifest.permission.BLUETOOTH) protected void finalize() { + if (mCloseGuard != null) { + mCloseGuard.warnIfOpen(); + } close(); } @@ -316,6 +326,7 @@ public final class BluetoothPan implements BluetoothProfile { * @hide */ @Override + @RequiresPermission(Manifest.permission.BLUETOOTH) public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { if (VDBG) log("getDevicesMatchingStates()"); final IBluetoothPan service = getService(); @@ -335,6 +346,7 @@ public final class BluetoothPan implements BluetoothProfile { * {@inheritDoc} */ @Override + @RequiresPermission(Manifest.permission.BLUETOOTH) public int getConnectionState(@Nullable BluetoothDevice device) { if (VDBG) log("getState(" + device + ")"); final IBluetoothPan service = getService(); @@ -355,6 +367,7 @@ public final class BluetoothPan implements BluetoothProfile { * * @param value is whether to enable or disable bluetooth tethering */ + @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) public void setBluetoothTethering(boolean value) { String pkgName = mContext.getOpPackageName(); if (DBG) log("setBluetoothTethering(" + value + "), calling package:" + pkgName); @@ -373,6 +386,7 @@ public final class BluetoothPan implements BluetoothProfile { * * @return true if tethering is on, false if not or some error occurred */ + @RequiresPermission(Manifest.permission.BLUETOOTH) public boolean isTetheringOn() { if (VDBG) log("isTetheringOn()"); final IBluetoothPan service = getService(); diff --git a/core/java/android/content/ApexContext.java b/core/java/android/content/ApexContext.java new file mode 100644 index 000000000000..fe5cedca4654 --- /dev/null +++ b/core/java/android/content/ApexContext.java @@ -0,0 +1,98 @@ +/* + * 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.content; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Environment; +import android.os.UserHandle; + +import java.io.File; +import java.util.Objects; + +/** + * Provides information about the environment for a particular APEX. + * + * @hide + */ +@SystemApi +public class ApexContext { + + private static final String APEX_DATA = "apexdata"; + + /** + * Returns an ApexContext instance for the APEX with the provided {@code apexModuleName}. + * + * <p>To preserve the safety and integrity of APEX modules, you must only obtain the ApexContext + * for your specific APEX, and you <em>must never</em> attempt to obtain an ApexContext for + * another APEX. Any coordination between APEXs must be performed through well-defined + * interfaces; attempting to directly read or write raw files belonging to another APEX will + * violate the hermetic storage requirements placed upon each module. + */ + @NonNull + public static ApexContext getApexContext(@NonNull String apexModuleName) { + Objects.requireNonNull(apexModuleName, "apexModuleName cannot be null"); + //TODO(b/141148175): Check that apexModuleName is an actual APEX name + return new ApexContext(apexModuleName); + } + + private final String mApexModuleName; + + private ApexContext(String apexModuleName) { + mApexModuleName = apexModuleName; + } + + /** + * Returns the data directory for the APEX in device-encrypted, non-user-specific storage. + * + * <p>This directory is automatically created by the system for installed APEXes, and its + * contents will be rolled back if the APEX is rolled back. + */ + @NonNull + public File getDeviceProtectedDataDir() { + return Environment.buildPath( + Environment.getDataMiscDirectory(), APEX_DATA, mApexModuleName); + } + + /** + * Returns the data directory for the APEX in device-encrypted, user-specific storage for the + * specified {@code user}. + * + * <p>This directory is automatically created by the system for each user and for each installed + * APEX, and its contents will be rolled back if the APEX is rolled back. + */ + @NonNull + public File getDeviceProtectedDataDirForUser(@NonNull UserHandle user) { + return Environment.buildPath( + Environment.getDataMiscDeDirectory(user.getIdentifier()), APEX_DATA, + mApexModuleName); + } + + /** + * Returns the data directory for the APEX in credential-encrypted, user-specific storage for + * the specified {@code user}. + * + * <p>This directory is automatically created by the system for each user and for each installed + * APEX, and its contents will be rolled back if the APEX is rolled back. + */ + @NonNull + public File getCredentialProtectedDataDirForUser(@NonNull UserHandle user) { + return Environment.buildPath( + Environment.getDataMiscCeDirectory(user.getIdentifier()), APEX_DATA, + mApexModuleName); + } +} diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index 85826fd4e669..c271e3c66adc 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -28,6 +28,7 @@ import static android.os.Trace.TRACE_TAG_DATABASE; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.app.AppOpsManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.PackageManager; @@ -2526,6 +2527,16 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } /** + * Returns the user associated with the given URI. + * + * @hide + */ + @TestApi + public @NonNull static UserHandle getUserHandleFromUri(@NonNull Uri uri) { + return UserHandle.of(getUserIdFromUri(uri, Process.myUserHandle().getIdentifier())); + } + + /** * Removes userId part from authority string. Expects format: * userId@some.authority * If there is no userId in the authority, it symply returns the argument diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 2943e398dd87..07b81ca7f21e 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3984,6 +3984,16 @@ public abstract class Context { /** * Use with {@link #getSystemService(String)} to retrieve a {@link + * android.net.ConnectivityDiagnosticsManager} for performing network connectivity diagnostics + * as well as receiving network connectivity information from the system. + * + * @see #getSystemService(String) + * @see android.net.ConnectivityDiagnosticsManager + */ + public static final String CONNECTIVITY_DIAGNOSTICS_SERVICE = "connectivity_diagnostics"; + + /** + * Use with {@link #getSystemService(String)} to retrieve a {@link * android.net.TestNetworkManager} for building TUNs and limited-use Networks * * @see #getSystemService(String) @@ -5720,6 +5730,63 @@ public abstract class Context { public abstract Context createDisplayContext(@NonNull Display display); /** + * Creates a Context for a non-activity window. + * + * <p> + * A window context is a context that can be used to add non-activity windows, such as + * {@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY}. A window context + * must be created from a context that has an associated {@link Display}, such as + * {@link android.app.Activity Activity} or a context created with + * {@link #createDisplayContext(Display)}. + * + * <p> + * The window context is created with the appropriate {@link Configuration} for the area of the + * display that the windows created with it can occupy; it must be used when + * {@link android.view.LayoutInflater inflating} views, such that they can be inflated with + * proper {@link Resources}. + * + * Below is a sample code to <b>add an application overlay window on the primary display:<b/> + * <pre class="prettyprint"> + * ... + * final DisplayManager dm = anyContext.getSystemService(DisplayManager.class); + * final Display primaryDisplay = dm.getDisplay(DEFAULT_DISPLAY); + * final Context windowContext = anyContext.createDisplayContext(primaryDisplay) + * .createWindowContext(TYPE_APPLICATION_OVERLAY); + * final View overlayView = Inflater.from(windowContext).inflate(someLayoutXml, null); + * + * // WindowManager.LayoutParams initialization + * ... + * mParams.type = TYPE_APPLICATION_OVERLAY; + * ... + * + * mWindowContext.getSystemService(WindowManager.class).addView(overlayView, mParams); + * </pre> + * + * <p> + * This context's configuration and resources are adjusted to a display area where the windows + * with provided type will be added. <b>Note that all windows associated with the same context + * will have an affinity and can only be moved together between different displays or areas on a + * display.</b> If there is a need to add different window types, or non-associated windows, + * separate Contexts should be used. + * </p> + * + * @param type Window type in {@link WindowManager.LayoutParams} + * @return A {@link Context} that can be used to create windows. + * @throws UnsupportedOperationException if this is called on a non-UI context, such as + * {@link android.app.Application Application} or {@link android.app.Service Service}. + * + * @see #getSystemService(String) + * @see #getSystemService(Class) + * @see #WINDOW_SERVICE + * @see #LAYOUT_INFLATER_SERVICE + * @see #WALLPAPER_SERVICE + * @throws IllegalArgumentException if token is invalid + */ + public @NonNull Context createWindowContext(int type) { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } + + /** * Return a new Context object for the current Context but for a different feature in the app. * Features can be used by complex apps to separate logical parts. * @@ -5803,17 +5870,22 @@ public abstract class Context { public abstract DisplayAdjustments getDisplayAdjustments(int displayId); /** + * Get the display this context is associated with. Applications should use this method with + * {@link android.app.Activity} or a context associated with a {@link Display} via + * {@link #createDisplayContext(Display)} to get a display object associated with a Context, or + * {@link android.hardware.display.DisplayManager#getDisplay} to get a display object by id. * @return Returns the {@link Display} object this context is associated with. - * @hide */ - @UnsupportedAppUsage - @TestApi - public abstract Display getDisplay(); + @Nullable + public Display getDisplay() { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } /** - * Gets the display ID. + * Gets the ID of the display this context is associated with. * * @return display ID associated with this {@link Context}. + * @see #getDisplay() * @hide */ @TestApi diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 6fe11873d327..b2b7988de896 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -977,6 +977,12 @@ public class ContextWrapper extends Context { } @Override + @NonNull + public Context createWindowContext(int type) { + return mBase.createWindowContext(type); + } + + @Override public @NonNull Context createFeatureContext(@Nullable String featureId) { return mBase.createFeatureContext(featureId); } @@ -992,11 +998,8 @@ public class ContextWrapper extends Context { return mBase.getDisplayAdjustments(displayId); } - /** @hide */ - @UnsupportedAppUsage - @TestApi @Override - public Display getDisplay() { + public @Nullable Display getDisplay() { return mBase.getDisplay(); } diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java index 48f473018127..de153d00b48b 100644 --- a/core/java/android/content/pm/CrossProfileApps.java +++ b/core/java/android/content/pm/CrossProfileApps.java @@ -34,8 +34,10 @@ import android.provider.Settings; import com.android.internal.R; import com.android.internal.util.UserIcons; +import java.util.Collection; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; /** * Class for handling cross profile operations. Apps can use this class to interact with its @@ -298,19 +300,24 @@ public class CrossProfileApps { * configurable by users in Settings. This configures it for the profile group of the calling * package. * - * <p>Before calling, check {@link #canRequestInteractAcrossProfiles()} and do not call if it is - * {@code false}. If presenting a user interface, do not allow the user to configure the app-op - * in that case. + * <p>Before calling, check {@link #canConfigureInteractAcrossProfiles(String)} and do not call + * if it is {@code false}. If presenting a user interface, do not allow the user to configure + * the app-op in that case. * * <p>The underlying app-op {@link android.app.AppOpsManager#OP_INTERACT_ACROSS_PROFILES} should * never be set directly. This method ensures that the app-op is kept in sync for the app across * each user in the profile group and that those apps are sent a broadcast when their ability to * interact across profiles changes. * - * <p>This method should be used whenever an app's ability to interact across profiles changes, - * as defined by the return value of {@link #canInteractAcrossProfiles()}. This includes user - * consent changes in Settings or during provisioning, plus changes to the admin or OEM consent - * whitelists that make the current app-op value invalid. + * <p>This method should be used directly whenever a user's action results in a change in an + * app's ability to interact across profiles, as defined by the return value of {@link + * #canInteractAcrossProfiles()}. This includes user consent changes in Settings or during + * provisioning. + * + * <p>If other changes could have affected the app's ability to interact across profiles, as + * defined by the return value of {@link #canInteractAcrossProfiles()}, such as changes to the + * admin or OEM consent whitelists, then {@link + * #resetInteractAcrossProfilesAppOpsIfInvalid(List)} should be used. * * @hide */ @@ -325,6 +332,58 @@ public class CrossProfileApps { } } + /** + * Returns whether the given package can have its ability to interact across profiles configured + * by the user. This means that every other condition to interact across profiles has been set. + * + * <p>This differs from {@link #canRequestInteractAcrossProfiles()} since it will not return + * {@code false} simply when the target profile is disabled. + * + * @hide + */ + public boolean canConfigureInteractAcrossProfiles(@NonNull String packageName) { + try { + return mService.canConfigureInteractAcrossProfiles(packageName); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** + * For each of the packages defined in {@code previousCrossProfilePackages} but not included in + * {@code newCrossProfilePackages}, resets the app-op for {@link android.Manifest.permission + * #INTERACT_ACROSS_PROFILES} back to its default value if it can no longer be configured by + * users in Settings, as defined by {@link #canConfigureInteractAcrossProfiles(String)}. + * + * <p>This method should be used whenever an app's ability to interact across profiles could + * have changed as a result of non-user actions, such as changes to admin or OEM consent + * whitelists. + * + * @hide + */ + @RequiresPermission( + allOf={android.Manifest.permission.MANAGE_APP_OPS_MODES, + android.Manifest.permission.INTERACT_ACROSS_USERS}) + public void resetInteractAcrossProfilesAppOps( + @NonNull Collection<String> previousCrossProfilePackages, + @NonNull Set<String> newCrossProfilePackages) { + if (previousCrossProfilePackages.isEmpty()) { + return; + } + final List<String> unsetCrossProfilePackages = + previousCrossProfilePackages.stream() + .filter(packageName -> !newCrossProfilePackages.contains(packageName)) + .collect(Collectors.toList()); + if (unsetCrossProfilePackages.isEmpty()) { + return; + } + try { + mService.resetInteractAcrossProfilesAppOps(unsetCrossProfilePackages); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + private void verifyCanAccessUser(UserHandle userHandle) { if (!getTargetUserProfiles().contains(userHandle)) { throw new SecurityException("Not allowed to access " + userHandle); diff --git a/core/java/android/content/pm/ICrossProfileApps.aidl b/core/java/android/content/pm/ICrossProfileApps.aidl index 755950cd5ebe..a69b9881aa01 100644 --- a/core/java/android/content/pm/ICrossProfileApps.aidl +++ b/core/java/android/content/pm/ICrossProfileApps.aidl @@ -35,4 +35,6 @@ interface ICrossProfileApps { boolean canInteractAcrossProfiles(in String callingPackage); boolean canRequestInteractAcrossProfiles(in String callingPackage); void setInteractAcrossProfilesAppOp(in String packageName, int newMode); + boolean canConfigureInteractAcrossProfiles(in String packageName); + void resetInteractAcrossProfilesAppOps(in List<String> packageNames); }
\ No newline at end of file diff --git a/core/java/android/content/pm/ModuleInfo.java b/core/java/android/content/pm/ModuleInfo.java index d930c92d66ed..a6db662bcc43 100644 --- a/core/java/android/content/pm/ModuleInfo.java +++ b/core/java/android/content/pm/ModuleInfo.java @@ -37,6 +37,12 @@ public final class ModuleInfo implements Parcelable { /** The package name of this module. */ private String mPackageName; + /** + * The name of the APEX this module is distributed as, or null if it is not distributed via + * APEX. + */ + @Nullable private String mApexModuleName; + /** Whether or not this module is hidden from the user. */ private boolean mHidden; @@ -54,6 +60,7 @@ public final class ModuleInfo implements Parcelable { mName = orig.mName; mPackageName = orig.mPackageName; mHidden = orig.mHidden; + mApexModuleName = orig.mApexModuleName; } /** @hide Sets the public name of this module. */ @@ -89,6 +96,17 @@ public final class ModuleInfo implements Parcelable { return mHidden; } + /** @hide Sets the apex module name. */ + public ModuleInfo setApexModuleName(@Nullable String apexModuleName) { + mApexModuleName = apexModuleName; + return this; + } + + /** @hide Gets the apex module name. */ + public @Nullable String getApexModuleName() { + return mApexModuleName; + } + /** Returns a string representation of this object. */ public String toString() { return "ModuleInfo{" @@ -106,6 +124,7 @@ public final class ModuleInfo implements Parcelable { int hashCode = 0; hashCode = 31 * hashCode + Objects.hashCode(mName); hashCode = 31 * hashCode + Objects.hashCode(mPackageName); + hashCode = 31 * hashCode + Objects.hashCode(mApexModuleName); hashCode = 31 * hashCode + Boolean.hashCode(mHidden); return hashCode; } @@ -118,6 +137,7 @@ public final class ModuleInfo implements Parcelable { final ModuleInfo other = (ModuleInfo) obj; return Objects.equals(mName, other.mName) && Objects.equals(mPackageName, other.mPackageName) + && Objects.equals(mApexModuleName, other.mApexModuleName) && mHidden == other.mHidden; } @@ -126,12 +146,14 @@ public final class ModuleInfo implements Parcelable { dest.writeCharSequence(mName); dest.writeString(mPackageName); dest.writeBoolean(mHidden); + dest.writeString(mApexModuleName); } private ModuleInfo(Parcel source) { mName = source.readCharSequence(); mPackageName = source.readString(); mHidden = source.readBoolean(); + mApexModuleName = source.readString(); } public static final @android.annotation.NonNull Parcelable.Creator<ModuleInfo> CREATOR = diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index f264adbbb592..fbcb2ddf4cad 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -2029,6 +2029,9 @@ public class PackageInstaller { public boolean isCommitted; /** {@hide} */ + public long createdMillis; + + /** {@hide} */ public long updatedMillis; /** {@hide} */ @@ -2078,6 +2081,7 @@ public class PackageInstaller { mStagedSessionErrorMessage = source.readString(); isCommitted = source.readBoolean(); rollbackDataPolicy = source.readInt(); + createdMillis = source.readLong(); } /** @@ -2520,6 +2524,13 @@ public class PackageInstaller { } /** + * The timestamp of the initial creation of the session. + */ + public long getCreatedMillis() { + return createdMillis; + } + + /** * The timestamp of the last update that occurred to the session, including changing of * states in case of staged sessions. */ @@ -2568,6 +2579,7 @@ public class PackageInstaller { dest.writeString(mStagedSessionErrorMessage); dest.writeBoolean(isCommitted); dest.writeInt(rollbackDataPolicy); + dest.writeLong(createdMillis); } public static final Parcelable.Creator<SessionInfo> diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 6694335f264c..a2d425be214c 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -230,7 +230,7 @@ public abstract class PackageManager { MATCH_ALL, }) @Retention(RetentionPolicy.SOURCE) - public @interface ModuleInfoFlags {} + public @interface InstalledModulesFlags {} /** @hide */ @IntDef(flag = true, prefix = { "GET_", "MATCH_" }, value = { @@ -582,6 +582,22 @@ public abstract class PackageManager { public static final int ONLY_IF_NO_MATCH_FOUND = 0x00000004; /** @hide */ + @IntDef(flag = true, prefix = { "MODULE_" }, value = { + MODULE_APEX_NAME, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ModuleInfoFlags {} + + /** + * Flag for {@link #getModuleInfo}: allow ModuleInfo to be retrieved using the apex module + * name, rather than the package name. + * + * @hide + */ + @SystemApi + public static final int MODULE_APEX_NAME = 0x00000001; + + /** @hide */ @IntDef(prefix = { "PERMISSION_" }, value = { PERMISSION_GRANTED, PERMISSION_DENIED @@ -2187,6 +2203,23 @@ public abstract class PackageManager { /** * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature(String, int)}: If this feature is supported, the feature version + * specifies a date such that the device is known to pass the Vulkan dEQP test suite associated + * with that date. The date is encoded as follows: + * <ul> + * <li>Year in bits 31-16</li> + * <li>Month in bits 15-8</li> + * <li>Day in bits 7-0</li> + * </ul> + * <p> + * Example: 2019-03-01 is encoded as 0x07E30301, and would indicate that the device passes the + * Vulkan dEQP test suite version that was current on 2019-03-01. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_VULKAN_DEQP_LEVEL = "android.software.vulkan.deqp.level"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device includes broadcast radio tuner. * @hide */ @@ -3929,7 +3962,7 @@ public abstract class PackageManager { * there are no installed modules, an empty list is returned. */ @NonNull - public List<ModuleInfo> getInstalledModules(@ModuleInfoFlags int flags) { + public List<ModuleInfo> getInstalledModules(@InstalledModulesFlags int flags) { throw new UnsupportedOperationException( "getInstalledModules not implemented in subclass"); } @@ -4394,14 +4427,17 @@ 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. + * Gets the localized label that corresponds to the option in settings for granting + * background access. + * + * <p>The intended use is for apps to reference this label in its instruction for users to grant + * a background permission. * - * @return the string shown on the button for granting background location + * @return the localized label that corresponds to the settings option for granting + * background access */ @NonNull - public CharSequence getBackgroundPermissionButtonLabel() { + public CharSequence getBackgroundPermissionOptionLabel() { return ""; } diff --git a/core/java/android/content/pm/ProcessInfo.java b/core/java/android/content/pm/ProcessInfo.java new file mode 100644 index 000000000000..c77a267958f5 --- /dev/null +++ b/core/java/android/content/pm/ProcessInfo.java @@ -0,0 +1,88 @@ +/* + * 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.content.pm; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; +import android.util.ArraySet; + +/** + * Information about a process an app may run. This corresponds to information collected from the + * AndroidManifest.xml's <permission-group> tags. + * @hide + */ +public class ProcessInfo implements Parcelable { + /** + * The name of the process, fully-qualified based on the app's package name. + */ + public String name; + + /** + * If non-null, these are permissions that are not allowed in this process. + */ + @Nullable + public ArraySet<String> deniedPermissions; + + public ProcessInfo(String name, ArraySet<String> deniedPermissions) { + this.name = name; + this.deniedPermissions = deniedPermissions; + } + + @Deprecated + public ProcessInfo(@NonNull ProcessInfo orig) { + this.name = orig.name; + this.deniedPermissions = orig.deniedPermissions; + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int parcelableFlags) { + dest.writeString(this.name); + final int numDenied = this.deniedPermissions != null + ? this.deniedPermissions.size() : 0; + dest.writeInt(numDenied); + for (int i = 0; i < numDenied; i++) { + dest.writeString(this.deniedPermissions.valueAt(i)); + } + } + + public static final @NonNull Creator<ProcessInfo> CREATOR = + new Creator<ProcessInfo>() { + public ProcessInfo createFromParcel(Parcel source) { + return new ProcessInfo(source); + } + public ProcessInfo[] newArray(int size) { + return new ProcessInfo[size]; + } + }; + + private ProcessInfo(Parcel source) { + this.name = source.readString(); + final int numDenied = source.readInt(); + if (numDenied > 0) { + this.deniedPermissions = new ArraySet<>(numDenied); + for (int i = numDenied - 1; i >= 0; i--) { + this.deniedPermissions.add(TextUtils.safeIntern(source.readString())); + } + } + } +} diff --git a/core/java/android/content/pm/parsing/AndroidPackage.java b/core/java/android/content/pm/parsing/AndroidPackage.java index 990c8359e698..fbe5a48ad61e 100644 --- a/core/java/android/content/pm/parsing/AndroidPackage.java +++ b/core/java/android/content/pm/parsing/AndroidPackage.java @@ -36,6 +36,7 @@ import android.content.pm.parsing.ComponentParseUtils.ParsedService; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.SparseArray; @@ -379,6 +380,9 @@ public interface AndroidPackage extends Parcelable { @Nullable long[] getUsesStaticLibrariesVersions(); + @Nullable + ArrayMap<String, ComponentParseUtils.ParsedProcess> getProcesses(); + int getVersionCode(); int getVersionCodeMajor(); diff --git a/core/java/android/content/pm/parsing/ApkParseUtils.java b/core/java/android/content/pm/parsing/ApkParseUtils.java index 9b069acd1b06..3018230fac27 100644 --- a/core/java/android/content/pm/parsing/ApkParseUtils.java +++ b/core/java/android/content/pm/parsing/ApkParseUtils.java @@ -2426,6 +2426,21 @@ public class ApkParseUtils { XmlUtils.skipCurrentTag(parser); break; + case "processes": + ArrayMap<String, ComponentParseUtils.ParsedProcess> processes = + ComponentParseUtils.parseProcesses(separateProcesses, + parsingPackage, + res, parser, flags, + outError); + if (processes == null) { + return parseInput.error( + PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, + outError[0] + ); + } + + parsingPackage.setProcesses(processes); + break; case "uses-package": // Dependencies for app installers; we don't currently try to // enforce this. diff --git a/core/java/android/content/pm/parsing/ComponentParseUtils.java b/core/java/android/content/pm/parsing/ComponentParseUtils.java index 3846202aa04d..9a0a6d54da50 100644 --- a/core/java/android/content/pm/parsing/ComponentParseUtils.java +++ b/core/java/android/content/pm/parsing/ComponentParseUtils.java @@ -50,6 +50,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.PatternMatcher; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.AttributeSet; import android.util.Log; @@ -1366,6 +1367,72 @@ public class ComponentParseUtils { }; } + public static class ParsedProcess implements Parcelable { + + public String name; + @Nullable + public ArraySet<String> deniedPermissions; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.name); + final int numDenied = this.deniedPermissions != null + ? this.deniedPermissions.size() : 0; + dest.writeInt(numDenied); + for (int i = 0; i < numDenied; i++) { + dest.writeString(this.deniedPermissions.valueAt(i)); + } + } + + public ParsedProcess() { + } + + public ParsedProcess(@NonNull ParsedProcess other) { + name = other.name; + if (other.deniedPermissions != null) { + deniedPermissions = new ArraySet<>(other.deniedPermissions); + } + } + + public void addStateFrom(@NonNull ParsedProcess other) { + if (other.deniedPermissions != null) { + for (int i = other.deniedPermissions.size() - 1; i >= 0; i--) { + if (deniedPermissions == null) { + deniedPermissions = new ArraySet<>(other.deniedPermissions.size()); + } + deniedPermissions.add(other.deniedPermissions.valueAt(i)); + } + } + } + + protected ParsedProcess(Parcel in) { + this.name = TextUtils.safeIntern(in.readString()); + final int numDenied = in.readInt(); + if (numDenied > 0) { + this.deniedPermissions = new ArraySet<>(numDenied); + this.deniedPermissions.add(TextUtils.safeIntern(in.readString())); + } + } + + public static final Creator<ParsedProcess> CREATOR = + new Creator<ParsedProcess>() { + @Override + public ParsedProcess createFromParcel(Parcel source) { + return new ParsedProcess(source); + } + + @Override + public ParsedProcess[] newArray(int size) { + return new ParsedProcess[size]; + } + }; + } + public static ParsedActivity parseActivity( String[] separateProcesses, ParsingPackage parsingPackage, @@ -3266,6 +3333,189 @@ public class ComponentParseUtils { return result; } + private static @Nullable ArraySet<String> parseDenyPermission( + ArraySet<String> perms, + Resources res, + XmlResourceParser parser, + String[] outError + ) throws IOException, XmlPullParserException { + TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestDenyPermission); + if (sa == null) { + outError[0] = "<deny-permission> could not be parsed"; + return null; + } + + try { + String perm = sa.getNonConfigurationString( + R.styleable.AndroidManifestDenyPermission_name,0); + if (perm != null && perm.equals(android.Manifest.permission.INTERNET)) { + if (perms == null) { + perms = new ArraySet<>(); + } + perms.add(perm); + } + } finally { + sa.recycle(); + } + XmlUtils.skipCurrentTag(parser); + return perms; + } + + private static ArraySet<String> parseAllowPermission( + ArraySet<String> perms, + Resources res, + XmlResourceParser parser, + String[] outError + ) throws IOException, XmlPullParserException { + TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestAllowPermission); + if (sa == null) { + outError[0] = "<allow-permission> could not be parsed"; + return null; + } + + try { + String perm = sa.getNonConfigurationString( + R.styleable.AndroidManifestAllowPermission_name,0); + if (perm != null && perm.equals(android.Manifest.permission.INTERNET) + && perms != null) { + perms.remove(perm); + if (perms.size() <= 0) { + perms = null; + } + } + } finally { + sa.recycle(); + } + XmlUtils.skipCurrentTag(parser); + return perms; + } + + public static ParsedProcess parseProcess( + ArraySet<String> perms, + String[] separateProcesses, + ParsingPackage parsingPackage, + Resources res, + XmlResourceParser parser, + int flags, + String[] outError + ) throws IOException, XmlPullParserException { + TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestProcess); + if (sa == null) { + outError[0] = "<process> could not be parsed"; + return null; + } + + ParsedProcess proc = new ParsedProcess(); + if (perms != null) { + proc.deniedPermissions = new ArraySet(perms); + } + + try { + proc.name = sa.getNonConfigurationString( + R.styleable.AndroidManifestProcess_process,0); + proc.name = PackageParser.buildProcessName(parsingPackage.getPackageName(), + null, proc.name, flags, separateProcesses, outError); + + if (proc.name == null || proc.name.length() <= 0) { + outError[0] = "<process> does not specify android:process"; + return null; + } + proc.name = PackageParser.buildProcessName(parsingPackage.getPackageName(), + parsingPackage.getPackageName(), proc.name, + flags, separateProcesses, outError); + if (outError[0] != null) { + return null; + } + } finally { + sa.recycle(); + } + + 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; + } + + String tagName = parser.getName(); + if (tagName.equals("deny-permission")) { + proc.deniedPermissions = parseDenyPermission(proc.deniedPermissions, res, parser, + outError); + if (outError[0] != null) { + return null; + } + } else if (tagName.equals("allow-permission")) { + proc.deniedPermissions = parseAllowPermission(proc.deniedPermissions, res, parser, + outError); + if (outError[0] != null) { + return null; + } + } else { + Slog.w(TAG, "Unknown element under <process>: " + tagName + + " at " + parsingPackage.getBaseCodePath() + " " + + parser.getPositionDescription()); + XmlUtils.skipCurrentTag(parser); + continue; + } + } + + return proc; + } + + public static ArrayMap<String, ParsedProcess> parseProcesses( + String[] separateProcesses, + ParsingPackage parsingPackage, + Resources res, + XmlResourceParser parser, + int flags, + String[] outError + ) throws IOException, XmlPullParserException { + ArraySet<String> deniedPerms = null; + ArrayMap<String, ParsedProcess> processes = new ArrayMap<>(); + + 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; + } + + String tagName = parser.getName(); + if (tagName.equals("deny-permission")) { + deniedPerms = parseDenyPermission(deniedPerms, res, parser, outError); + if (outError[0] != null) { + return null; + } + } else if (tagName.equals("allow-permission")) { + deniedPerms = parseAllowPermission(deniedPerms, res, parser, outError); + if (outError[0] != null) { + return null; + } + } else if (tagName.equals("process")) { + ParsedProcess proc = parseProcess(deniedPerms, separateProcesses, parsingPackage, + res, parser, flags, outError); + if (outError[0] != null) { + return null; + } + if (processes.get(proc.name) != null) { + outError[0] = "<process> specified existing name '" + proc.name + "'"; + return null; + } + processes.put(proc.name, proc); + } else { + Slog.w(TAG, "Unknown element under <processes>: " + tagName + + " at " + parsingPackage.getBaseCodePath() + " " + + parser.getPositionDescription()); + XmlUtils.skipCurrentTag(parser); + continue; + } + } + + return processes; + } + public static ActivityInfo.WindowLayout parseLayout(Resources res, AttributeSet attrs) { TypedArray sw = res.obtainAttributes(attrs, R.styleable.AndroidManifestLayout); diff --git a/core/java/android/content/pm/parsing/PackageImpl.java b/core/java/android/content/pm/parsing/PackageImpl.java index 8677fced18fa..9baf3258a230 100644 --- a/core/java/android/content/pm/parsing/PackageImpl.java +++ b/core/java/android/content/pm/parsing/PackageImpl.java @@ -215,6 +215,9 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android @Nullable private ArrayList<String> queriesPackages; + @Nullable + private ArrayMap<String, ComponentParseUtils.ParsedProcess> processes; + private String[] splitClassLoaderNames; private String[] splitCodePaths; private SparseArray<int[]> splitDependencies; @@ -527,6 +530,12 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android return usesStaticLibraries; } + @Nullable + @Override + public ArrayMap<String, ComponentParseUtils.ParsedProcess> getProcesses() { + return processes; + } + @Override public boolean isBaseHardwareAccelerated() { return baseHardwareAccelerated; @@ -948,6 +957,12 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android } @Override + public PackageImpl setProcesses(ArrayMap<String, ComponentParseUtils.ParsedProcess> processes) { + this.processes = processes; + return this; + } + + @Override public PackageImpl setSupportsSmallScreens(int supportsSmallScreens) { if (supportsSmallScreens == 1) { return this; @@ -3010,6 +3025,11 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android dest.writeStringList(this.usesOptionalLibraries); dest.writeStringList(this.usesStaticLibraries); dest.writeLongArray(this.usesStaticLibrariesVersions); + final int numProcesses = this.processes != null ? this.processes.size() : 0; + dest.writeInt(numProcesses); + for (int i = 0; i < numProcesses; i++) { + this.processes.valueAt(i).writeToParcel(dest, 0); + } if (this.usesStaticLibrariesCertDigests == null) { dest.writeInt(-1); @@ -3161,6 +3181,16 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android this.usesStaticLibraries = in.createStringArrayList(); internStringArrayList(usesStaticLibraries); this.usesStaticLibrariesVersions = in.createLongArray(); + final int numProcesses = in.readInt(); + if (numProcesses > 0) { + this.processes = new ArrayMap<>(numProcesses); + for (int i = 0; i < numProcesses; i++) { + ComponentParseUtils.ParsedProcess proc = new ComponentParseUtils.ParsedProcess(in); + this.processes.put(proc.name, proc); + } + } else { + this.processes = null; + } int digestsSize = in.readInt(); if (digestsSize >= 0) { diff --git a/core/java/android/content/pm/parsing/PackageInfoUtils.java b/core/java/android/content/pm/parsing/PackageInfoUtils.java index e0ba99bb4937..72df18998470 100644 --- a/core/java/android/content/pm/parsing/PackageInfoUtils.java +++ b/core/java/android/content/pm/parsing/PackageInfoUtils.java @@ -32,6 +32,7 @@ import android.content.pm.PackageParser; import android.content.pm.PackageUserState; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; +import android.content.pm.ProcessInfo; import android.content.pm.ProviderInfo; import android.content.pm.SELinuxUtil; import android.content.pm.ServiceInfo; @@ -41,11 +42,11 @@ import android.content.pm.parsing.ComponentParseUtils.ParsedActivity; import android.content.pm.parsing.ComponentParseUtils.ParsedInstrumentation; import android.content.pm.parsing.ComponentParseUtils.ParsedPermission; import android.content.pm.parsing.ComponentParseUtils.ParsedPermissionGroup; +import android.util.ArrayMap; import android.util.ArraySet; import com.android.internal.util.ArrayUtils; -import java.util.LinkedHashSet; import java.util.Set; /** @hide */ @@ -459,6 +460,24 @@ public class PackageInfoUtils { return ii; } + public static ArrayMap<String, ProcessInfo> generateProcessInfo( + ArrayMap<String, ComponentParseUtils.ParsedProcess> procs, + @PackageManager.ComponentInfoFlags int flags) { + if (procs == null) { + return null; + } + + final int numProcs = procs.size(); + ArrayMap<String, ProcessInfo> retProcs = new ArrayMap(numProcs); + for (int i = 0; i < numProcs; i++) { + ComponentParseUtils.ParsedProcess proc = procs.valueAt(i); + retProcs.put(proc.name, new ProcessInfo(proc.name, + proc.deniedPermissions != null + ? new ArraySet<>(proc.deniedPermissions) : null)); + } + return retProcs; + } + public static PermissionInfo generatePermissionInfo(ParsedPermission p, @PackageManager.ComponentInfoFlags int flags) { if (p == null) return null; diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java index 411c74991594..9ddcc0995fd4 100644 --- a/core/java/android/content/pm/parsing/ParsingPackage.java +++ b/core/java/android/content/pm/parsing/ParsingPackage.java @@ -31,6 +31,7 @@ import android.content.pm.parsing.ComponentParseUtils.ParsedPermissionGroup; import android.content.pm.parsing.ComponentParseUtils.ParsedProvider; import android.content.pm.parsing.ComponentParseUtils.ParsedService; import android.os.Bundle; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.SparseArray; @@ -99,6 +100,8 @@ public interface ParsingPackage extends AndroidPackage { ParsingPackage addQueriesPackage(String packageName); + ParsingPackage setProcesses(ArrayMap<String, ComponentParseUtils.ParsedProcess> processes); + ParsingPackage asSplit( String[] splitNames, String[] splitCodePaths, diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java index d43a619a7c1f..a30fd6b51e76 100644 --- a/core/java/android/hardware/soundtrigger/ConversionUtil.java +++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java @@ -130,7 +130,7 @@ class ConversionUtil { aidlPhrase.id = apiPhrase.id; aidlPhrase.recognitionModes = api2aidlRecognitionModes(apiPhrase.recognitionModes); aidlPhrase.users = Arrays.copyOf(apiPhrase.users, apiPhrase.users.length); - aidlPhrase.locale = apiPhrase.locale; + aidlPhrase.locale = apiPhrase.locale.toLanguageTag(); aidlPhrase.text = apiPhrase.text; return aidlPhrase; } diff --git a/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java b/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java index eb5d0cb539a1..ef76c620f3f3 100644 --- a/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java +++ b/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java @@ -17,6 +17,7 @@ package android.hardware.soundtrigger; import android.Manifest; +import android.annotation.IntDef; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -24,7 +25,6 @@ import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; -import android.service.voice.AlwaysOnHotwordDetector; import android.text.TextUtils; import android.util.ArraySet; import android.util.AttributeSet; @@ -35,6 +35,8 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; @@ -66,9 +68,10 @@ public class KeyphraseEnrollmentInfo { "com.android.intent.action.MANAGE_VOICE_KEYPHRASES"; /** * Intent extra: The intent extra for the specific manage action that needs to be performed. - * Possible values are {@link AlwaysOnHotwordDetector#MANAGE_ACTION_ENROLL}, - * {@link AlwaysOnHotwordDetector#MANAGE_ACTION_RE_ENROLL} - * or {@link AlwaysOnHotwordDetector#MANAGE_ACTION_UN_ENROLL}. + * + * @see #MANAGE_ACTION_ENROLL + * @see #MANAGE_ACTION_RE_ENROLL + * @see #MANAGE_ACTION_UN_ENROLL */ public static final String EXTRA_VOICE_KEYPHRASE_ACTION = "com.android.intent.extra.VOICE_KEYPHRASE_ACTION"; @@ -86,6 +89,31 @@ public class KeyphraseEnrollmentInfo { "com.android.intent.extra.VOICE_KEYPHRASE_LOCALE"; /** + * Keyphrase management actions used with the {@link #EXTRA_VOICE_KEYPHRASE_ACTION} intent extra + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "MANAGE_ACTION_" }, value = { + MANAGE_ACTION_ENROLL, + MANAGE_ACTION_RE_ENROLL, + MANAGE_ACTION_UN_ENROLL + }) + public @interface ManageActions {} + + /** + * Indicates desired action to enroll keyphrase model + */ + public static final int MANAGE_ACTION_ENROLL = 0; + /** + * Indicates desired action to re-enroll keyphrase model + */ + public static final int MANAGE_ACTION_RE_ENROLL = 1; + /** + * Indicates desired action to un-enroll keyphrase model + */ + public static final int MANAGE_ACTION_UN_ENROLL = 2; + + /** * List of available keyphrases. */ final private KeyphraseMetadata[] mKeyphrases; @@ -294,15 +322,13 @@ public class KeyphraseEnrollmentInfo { * for the locale. * * @param action The enrollment related action that this intent is supposed to perform. - * This can be one of {@link AlwaysOnHotwordDetector#MANAGE_ACTION_ENROLL}, - * {@link AlwaysOnHotwordDetector#MANAGE_ACTION_RE_ENROLL} - * or {@link AlwaysOnHotwordDetector#MANAGE_ACTION_UN_ENROLL} * @param keyphrase The keyphrase that the user needs to be enrolled to. * @param locale The locale for which the enrollment needs to be performed. * @return An {@link Intent} to manage the keyphrase. This can be null if managing the * given keyphrase/locale combination isn't possible. */ - public Intent getManageKeyphraseIntent(int action, String keyphrase, Locale locale) { + public Intent getManageKeyphraseIntent(@ManageActions int action, String keyphrase, + Locale locale) { if (mKeyphrasePackageMap == null || mKeyphrasePackageMap.isEmpty()) { Slog.w(TAG, "No enrollment application exists"); return null; diff --git a/core/java/android/hardware/soundtrigger/KeyphraseMetadata.aidl b/core/java/android/hardware/soundtrigger/KeyphraseMetadata.aidl new file mode 100644 index 000000000000..7a5e932bb7a0 --- /dev/null +++ b/core/java/android/hardware/soundtrigger/KeyphraseMetadata.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.hardware.soundtrigger; + +parcelable KeyphraseMetadata; diff --git a/core/java/android/hardware/soundtrigger/KeyphraseMetadata.java b/core/java/android/hardware/soundtrigger/KeyphraseMetadata.java index ed8c296e572f..15462deea158 100644 --- a/core/java/android/hardware/soundtrigger/KeyphraseMetadata.java +++ b/core/java/android/hardware/soundtrigger/KeyphraseMetadata.java @@ -16,8 +16,13 @@ package android.hardware.soundtrigger; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcelable; import android.util.ArraySet; +import com.android.internal.util.DataClass; + import java.util.Locale; /** @@ -25,37 +30,168 @@ import java.util.Locale; * * @hide */ -public class KeyphraseMetadata { +@DataClass( + genEqualsHashCode = true, + genToString = true, + genConstructor = false, + genHiddenConstDefs = true) +public final class KeyphraseMetadata implements Parcelable { public final int id; + @NonNull public final String keyphrase; + @NonNull public final ArraySet<Locale> supportedLocales; public final int recognitionModeFlags; - public KeyphraseMetadata(int id, String keyphrase, ArraySet<Locale> supportedLocales, - int recognitionModeFlags) { + public KeyphraseMetadata(int id, @NonNull String keyphrase, + @NonNull ArraySet<Locale> supportedLocales, int recognitionModeFlags) { this.id = id; this.keyphrase = keyphrase; this.supportedLocales = supportedLocales; this.recognitionModeFlags = recognitionModeFlags; } - @Override - public String toString() { - return "id=" + id + ", keyphrase=" + keyphrase + ", supported-locales=" + supportedLocales - + ", recognition-modes=" + recognitionModeFlags; - } - /** * @return Indicates if we support the given phrase. */ - public boolean supportsPhrase(String phrase) { + public boolean supportsPhrase(@Nullable String phrase) { return keyphrase.isEmpty() || keyphrase.equalsIgnoreCase(phrase); } /** * @return Indicates if we support the given locale. */ - public boolean supportsLocale(Locale locale) { + public boolean supportsLocale(@Nullable Locale locale) { return supportedLocales.isEmpty() || supportedLocales.contains(locale); } + + + + + // Code below generated by codegen v1.0.14. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/hardware/soundtrigger/KeyphraseMetadata.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "KeyphraseMetadata { " + + "id = " + id + ", " + + "keyphrase = " + keyphrase + ", " + + "supportedLocales = " + supportedLocales + ", " + + "recognitionModeFlags = " + recognitionModeFlags + + " }"; + } + + @Override + @DataClass.Generated.Member + public boolean equals(@Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(KeyphraseMetadata other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + KeyphraseMetadata that = (KeyphraseMetadata) o; + //noinspection PointlessBooleanExpression + return true + && id == that.id + && java.util.Objects.equals(keyphrase, that.keyphrase) + && java.util.Objects.equals(supportedLocales, that.supportedLocales) + && recognitionModeFlags == that.recognitionModeFlags; + } + + @Override + @DataClass.Generated.Member + public int hashCode() { + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + + int _hash = 1; + _hash = 31 * _hash + id; + _hash = 31 * _hash + java.util.Objects.hashCode(keyphrase); + _hash = 31 * _hash + java.util.Objects.hashCode(supportedLocales); + _hash = 31 * _hash + recognitionModeFlags; + return _hash; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + dest.writeInt(id); + dest.writeString(keyphrase); + dest.writeArraySet(supportedLocales); + dest.writeInt(recognitionModeFlags); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ KeyphraseMetadata(@NonNull android.os.Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + int _id = in.readInt(); + String _keyphrase = in.readString(); + ArraySet<Locale> _supportedLocales = (ArraySet) in.readArraySet(null); + int _recognitionModeFlags = in.readInt(); + + this.id = _id; + this.keyphrase = _keyphrase; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, keyphrase); + this.supportedLocales = _supportedLocales; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, supportedLocales); + this.recognitionModeFlags = _recognitionModeFlags; + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<KeyphraseMetadata> CREATOR + = new Parcelable.Creator<KeyphraseMetadata>() { + @Override + public KeyphraseMetadata[] newArray(int size) { + return new KeyphraseMetadata[size]; + } + + @Override + public KeyphraseMetadata createFromParcel(@NonNull android.os.Parcel in) { + return new KeyphraseMetadata(in); + } + }; + + @DataClass.Generated( + time = 1579290593964L, + codegenVersion = "1.0.14", + sourceFile = "frameworks/base/core/java/android/hardware/soundtrigger/KeyphraseMetadata.java", + inputSignatures = "public final int id\npublic final @android.annotation.NonNull java.lang.String keyphrase\npublic final @android.annotation.NonNull android.util.ArraySet<java.util.Locale> supportedLocales\npublic final int recognitionModeFlags\npublic boolean supportsPhrase(java.lang.String)\npublic boolean supportsLocale(java.util.Locale)\nclass KeyphraseMetadata extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genConstructor=false, genHiddenConstDefs=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + } diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java index 1932f46975c5..d505ae59dfaf 100644 --- a/core/java/android/hardware/soundtrigger/SoundTrigger.java +++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java @@ -49,6 +49,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; +import java.util.Locale; import java.util.UUID; /** @@ -148,6 +149,7 @@ public class SoundTrigger { public final int maxUsers; /** Supported recognition modes (bit field, RECOGNITION_MODE_VOICE_TRIGGER ...) */ + @RecognitionModes public final int recognitionModes; /** Supports seamless transition to capture mode after recognition */ @@ -175,9 +177,9 @@ public class SoundTrigger { ModuleProperties(int id, @NonNull String implementor, @NonNull String description, @NonNull String uuid, int version, @NonNull String supportedModelArch, - int maxSoundModels, int maxKeyphrases, int maxUsers, int recognitionModes, - boolean supportsCaptureTransition, int maxBufferMs, - boolean supportsConcurrentCapture, int powerConsumptionMw, + int maxSoundModels, int maxKeyphrases, int maxUsers, + @RecognitionModes int recognitionModes, boolean supportsCaptureTransition, + int maxBufferMs, boolean supportsConcurrentCapture, int powerConsumptionMw, boolean returnsTriggerInEvent, int audioCapabilities) { this.id = id; this.implementor = requireNonNull(implementor); @@ -271,16 +273,27 @@ public class SoundTrigger { } } - /***************************************************************************** + /** * A SoundModel describes the attributes and contains the binary data used by the hardware * implementation to detect a particular sound pattern. * A specialized version {@link KeyphraseSoundModel} is defined for key phrase * sound models. - * - * @hide - ****************************************************************************/ + */ public static class SoundModel { - /** Undefined sound model type */ + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + TYPE_GENERIC_SOUND, + TYPE_KEYPHRASE, + TYPE_UNKNOWN, + }) + public @interface SoundModelType {} + + /** + * Undefined sound model type + * @hide + */ public static final int TYPE_UNKNOWN = -1; /** Keyphrase sound model */ @@ -293,15 +306,14 @@ public class SoundTrigger { public static final int TYPE_GENERIC_SOUND = 1; /** Unique sound model identifier */ - @UnsupportedAppUsage @NonNull public final UUID uuid; /** Sound model type (e.g. TYPE_KEYPHRASE); */ + @SoundModelType public final int type; /** Unique sound model vendor identifier */ - @UnsupportedAppUsage @NonNull public final UUID vendorUuid; @@ -309,11 +321,11 @@ public class SoundTrigger { public final int version; /** Opaque data. For use by vendor implementation and enrollment application */ - @UnsupportedAppUsage @NonNull public final byte[] data; - public SoundModel(@NonNull UUID uuid, @Nullable UUID vendorUuid, int type, + /** @hide */ + public SoundModel(@NonNull UUID uuid, @Nullable UUID vendorUuid, @SoundModelType int type, @Nullable byte[] data, int version) { this.uuid = requireNonNull(uuid); this.vendorUuid = vendorUuid != null ? vendorUuid : new UUID(0, 0); @@ -336,67 +348,90 @@ public class SoundTrigger { @Override public boolean equals(Object obj) { - if (this == obj) + if (this == obj) { return true; - if (obj == null) + } + if (obj == null) { return false; - if (!(obj instanceof SoundModel)) + } + if (!(obj instanceof SoundModel)) { return false; + } SoundModel other = (SoundModel) obj; - if (type != other.type) + if (type != other.type) { return false; + } if (uuid == null) { - if (other.uuid != null) + if (other.uuid != null) { return false; - } else if (!uuid.equals(other.uuid)) + } + } else if (!uuid.equals(other.uuid)) { return false; + } if (vendorUuid == null) { - if (other.vendorUuid != null) + if (other.vendorUuid != null) { return false; - } else if (!vendorUuid.equals(other.vendorUuid)) + } + } else if (!vendorUuid.equals(other.vendorUuid)) { return false; - if (!Arrays.equals(data, other.data)) + } + if (!Arrays.equals(data, other.data)) { return false; - if (version != other.version) + } + if (version != other.version) { return false; + } return true; } } - /***************************************************************************** + /** * A Keyphrase describes a key phrase that can be detected by a * {@link KeyphraseSoundModel} - * - * @hide - ****************************************************************************/ - public static class Keyphrase implements Parcelable { + */ + public static final class Keyphrase implements Parcelable { /** Unique identifier for this keyphrase */ - @UnsupportedAppUsage public final int id; - /** Recognition modes supported for this key phrase in the model */ - @UnsupportedAppUsage + /** + * Recognition modes supported for this key phrase in the model + * + * @see #RECOGNITION_MODE_VOICE_TRIGGER + * @see #RECOGNITION_MODE_USER_IDENTIFICATION + * @see #RECOGNITION_MODE_USER_AUTHENTICATION + * @see #RECOGNITION_MODE_GENERIC + */ + @RecognitionModes public final int recognitionModes; - /** Locale of the keyphrase. JAVA Locale string e.g en_US */ - @UnsupportedAppUsage + /** Locale of the keyphrase. */ @NonNull - public final String locale; + public final Locale locale; /** Key phrase text */ - @UnsupportedAppUsage @NonNull public final String text; - /** Users this key phrase has been trained for. countains sound trigger specific user IDs - * derived from system user IDs {@link android.os.UserHandle#getIdentifier()}. */ - @UnsupportedAppUsage + /** + * Users this key phrase has been trained for. countains sound trigger specific user IDs + * derived from system user IDs {@link android.os.UserHandle#getIdentifier()}. + */ @NonNull public final int[] users; - @UnsupportedAppUsage - public Keyphrase(int id, int recognitionModes, @NonNull String locale, @NonNull String text, - @Nullable int[] users) { + /** + * Constructor for Keyphrase describes a key phrase that can be detected by a + * {@link KeyphraseSoundModel} + * + * @param id Unique keyphrase identifier for this keyphrase + * @param recognitionModes Bit field representation of recognition modes this keyphrase + * supports + * @param locale Locale of the keyphrase + * @param text Key phrase text + * @param users Users this key phrase has been trained for. + */ + public Keyphrase(int id, @RecognitionModes int recognitionModes, @NonNull Locale locale, + @NonNull String text, @Nullable int[] users) { this.id = id; this.recognitionModes = recognitionModes; this.locale = requireNonNull(locale); @@ -404,21 +439,27 @@ public class SoundTrigger { this.users = users != null ? users : new int[0]; } - public static final @android.annotation.NonNull Parcelable.Creator<Keyphrase> CREATOR - = new Parcelable.Creator<Keyphrase>() { - public Keyphrase createFromParcel(Parcel in) { - return Keyphrase.fromParcel(in); + public static final @NonNull Parcelable.Creator<Keyphrase> CREATOR = + new Parcelable.Creator<Keyphrase>() { + @NonNull + public Keyphrase createFromParcel(@NonNull Parcel in) { + return Keyphrase.readFromParcel(in); } + @NonNull public Keyphrase[] newArray(int size) { return new Keyphrase[size]; } }; - private static Keyphrase fromParcel(Parcel in) { + /** + * Read from Parcel to generate keyphrase + */ + @NonNull + public static Keyphrase readFromParcel(@NonNull Parcel in) { int id = in.readInt(); int recognitionModes = in.readInt(); - String locale = in.readString(); + Locale locale = Locale.forLanguageTag(in.readString()); String text = in.readString(); int[] users = null; int numUsers = in.readInt(); @@ -430,10 +471,10 @@ public class SoundTrigger { } @Override - public void writeToParcel(Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(id); dest.writeInt(recognitionModes); - dest.writeString(locale); + dest.writeString(locale.toLanguageTag()); dest.writeString(text); if (users != null) { dest.writeInt(users.length); @@ -443,6 +484,7 @@ public class SoundTrigger { } } + /** @hide */ @Override public int describeContents() { return 0; @@ -462,49 +504,57 @@ public class SoundTrigger { @Override public boolean equals(Object obj) { - if (this == obj) + if (this == obj) { return true; - if (obj == null) + } + if (obj == null) { return false; - if (getClass() != obj.getClass()) + } + if (getClass() != obj.getClass()) { return false; + } Keyphrase other = (Keyphrase) obj; if (text == null) { - if (other.text != null) + if (other.text != null) { return false; - } else if (!text.equals(other.text)) + } + } else if (!text.equals(other.text)) { return false; - if (id != other.id) + } + if (id != other.id) { return false; + } if (locale == null) { - if (other.locale != null) + if (other.locale != null) { return false; - } else if (!locale.equals(other.locale)) + } + } else if (!locale.equals(other.locale)) { return false; - if (recognitionModes != other.recognitionModes) + } + if (recognitionModes != other.recognitionModes) { return false; - if (!Arrays.equals(users, other.users)) + } + if (!Arrays.equals(users, other.users)) { return false; + } return true; } @Override public String toString() { - return "Keyphrase [id=" + id + ", recognitionModes=" + recognitionModes + ", locale=" - + locale + ", text=" + text + ", users=" + Arrays.toString(users) + "]"; + return "Keyphrase [id=" + id + ", recognitionModes=" + recognitionModes + + ", locale=" + locale.toLanguageTag() + ", text=" + text + + ", users=" + Arrays.toString(users) + "]"; } } - /***************************************************************************** + /** * A KeyphraseSoundModel is a specialized {@link SoundModel} for key phrases. * It contains data needed by the hardware to detect a certain number of key phrases * and the list of corresponding {@link Keyphrase} descriptors. - * - * @hide - ****************************************************************************/ - public static class KeyphraseSoundModel extends SoundModel implements Parcelable { + */ + public static final class KeyphraseSoundModel extends SoundModel implements Parcelable { /** Key phrases in this sound model */ - @UnsupportedAppUsage @NonNull public final Keyphrase[] keyphrases; // keyword phrases in model @@ -515,24 +565,29 @@ public class SoundTrigger { this.keyphrases = keyphrases != null ? keyphrases : new Keyphrase[0]; } - @UnsupportedAppUsage public KeyphraseSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid, @Nullable byte[] data, @Nullable Keyphrase[] keyphrases) { this(uuid, vendorUuid, data, keyphrases, -1); } - public static final @android.annotation.NonNull Parcelable.Creator<KeyphraseSoundModel> CREATOR - = new Parcelable.Creator<KeyphraseSoundModel>() { - public KeyphraseSoundModel createFromParcel(Parcel in) { - return KeyphraseSoundModel.fromParcel(in); + public static final @NonNull Parcelable.Creator<KeyphraseSoundModel> CREATOR = + new Parcelable.Creator<KeyphraseSoundModel>() { + @NonNull + public KeyphraseSoundModel createFromParcel(@NonNull Parcel in) { + return KeyphraseSoundModel.readFromParcel(in); } + @NonNull public KeyphraseSoundModel[] newArray(int size) { return new KeyphraseSoundModel[size]; } }; - private static KeyphraseSoundModel fromParcel(Parcel in) { + /** + * Read from Parcel to generate KeyphraseSoundModel + */ + @NonNull + public static KeyphraseSoundModel readFromParcel(@NonNull Parcel in) { UUID uuid = UUID.fromString(in.readString()); UUID vendorUuid = null; int length = in.readInt(); @@ -545,13 +600,14 @@ public class SoundTrigger { return new KeyphraseSoundModel(uuid, vendorUuid, data, keyphrases, version); } + /** @hide */ @Override public int describeContents() { return 0; } @Override - public void writeToParcel(Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeString(uuid.toString()); if (vendorUuid == null) { dest.writeInt(-1); @@ -583,15 +639,19 @@ public class SoundTrigger { @Override public boolean equals(Object obj) { - if (this == obj) + if (this == obj) { return true; - if (!super.equals(obj)) + } + if (!super.equals(obj)) { return false; - if (!(obj instanceof KeyphraseSoundModel)) + } + if (!(obj instanceof KeyphraseSoundModel)) { return false; + } KeyphraseSoundModel other = (KeyphraseSoundModel) obj; - if (!Arrays.equals(keyphrases, other.keyphrases)) + if (!Arrays.equals(keyphrases, other.keyphrases)) { return false; + } return true; } } @@ -760,31 +820,32 @@ public class SoundTrigger { } /** - * Modes for key phrase recognition + * Modes for key phrase recognition + * @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = { "RECOGNITION_MODE_" }, value = { + RECOGNITION_MODE_VOICE_TRIGGER, + RECOGNITION_MODE_USER_IDENTIFICATION, + RECOGNITION_MODE_USER_AUTHENTICATION, + RECOGNITION_MODE_GENERIC + }) + public @interface RecognitionModes {} /** - * Simple recognition of the key phrase - * - * @hide + * Trigger on recognition of a key phrase */ public static final int RECOGNITION_MODE_VOICE_TRIGGER = 0x1; /** * Trigger only if one user is identified - * - * @hide */ public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 0x2; /** * Trigger only if one user is authenticated - * - * @hide */ public static final int RECOGNITION_MODE_USER_AUTHENTICATION = 0x4; /** * Generic (non-speech) recognition. - * - * @hide */ public static final int RECOGNITION_MODE_GENERIC = 0x8; diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl index c8dbd16005ac..e32865ebeced 100644 --- a/core/java/android/hardware/usb/IUsbManager.aidl +++ b/core/java/android/hardware/usb/IUsbManager.aidl @@ -126,6 +126,9 @@ interface IUsbManager /* Gets the current screen unlocked functions. */ long getScreenUnlockedFunctions(); + /* Resets the USB gadget. */ + void resetUsbGadget(); + /* Get the functionfs control handle for the given function. Usb * descriptors will already be written, and the handle will be * ready to use. diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java index 67fdda37ed3b..827353b1802c 100644 --- a/core/java/android/hardware/usb/UsbManager.java +++ b/core/java/android/hardware/usb/UsbManager.java @@ -794,6 +794,25 @@ public class UsbManager { } /** + * Resets the USB Gadget. + * <p> + * Performs USB data stack reset through USB Gadget HAL. + * It will force USB data connection reset. The connection will disconnect and reconnect. + * </p> + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.MANAGE_USB) + public void resetUsbGadget() { + try { + mService.resetUsbGadget(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Returns a list of physical USB ports on the device. * <p> * This list is guaranteed to contain all dual-role USB Type C ports but it might diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 81a0d629380a..c5f0aab3daa9 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -20,8 +20,8 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE; +import static android.view.WindowInsets.Type.navigationBars; import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; @@ -1240,16 +1240,15 @@ public class InputMethodService extends AbstractInputMethodService { Context.LAYOUT_INFLATER_SERVICE); mWindow = new SoftInputWindow(this, "InputMethod", mTheme, null, null, mDispatcherState, WindowManager.LayoutParams.TYPE_INPUT_METHOD, Gravity.BOTTOM, false); - mWindow.getWindow().setFitWindowInsetsTypes(WindowInsets.Type.systemBars()); - mWindow.getWindow().addPrivateFlags(PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND); + mWindow.getWindow().getAttributes().setFitWindowInsetsTypes(WindowInsets.Type.statusBars()); + + // IME layout should always be inset by navigation bar, no matter it's current visibility. mWindow.getWindow().getDecorView().setOnApplyWindowInsetsListener( (v, insets) -> v.onApplyWindowInsets( - new WindowInsets.Builder(insets).setSystemWindowInsets( - android.graphics.Insets.of( - insets.getSystemWindowInsetLeft(), - insets.getSystemWindowInsetTop(), - insets.getSystemWindowInsetRight(), - insets.getStableInsetBottom())).build())); + new WindowInsets.Builder(insets).setInsets( + navigationBars(), insets.getMaxInsets(navigationBars())) + .build())); + // For ColorView in DecorView to work, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS needs to be set // by default (but IME developers can opt this out later if they want a new behavior). mWindow.getWindow().setFlags( diff --git a/core/java/android/net/CaptivePortalData.aidl b/core/java/android/net/CaptivePortalData.aidl new file mode 100644 index 000000000000..1d57ee759136 --- /dev/null +++ b/core/java/android/net/CaptivePortalData.aidl @@ -0,0 +1,19 @@ +/* + * 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.net; + +@JavaOnlyStableParcelable parcelable CaptivePortalData; diff --git a/core/java/android/net/CaptivePortalData.java b/core/java/android/net/CaptivePortalData.java new file mode 100644 index 000000000000..1357803a6cb8 --- /dev/null +++ b/core/java/android/net/CaptivePortalData.java @@ -0,0 +1,281 @@ +/* + * 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.net; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.annotation.TestApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Metadata sent by captive portals, see https://www.ietf.org/id/draft-ietf-capport-api-03.txt. + * @hide + */ +@SystemApi +@TestApi +public final class CaptivePortalData implements Parcelable { + private final long mRefreshTimeMillis; + @Nullable + private final Uri mUserPortalUrl; + @Nullable + private final Uri mVenueInfoUrl; + private final boolean mIsSessionExtendable; + private final long mByteLimit; + private final long mExpiryTimeMillis; + private final boolean mCaptive; + + private CaptivePortalData(long refreshTimeMillis, Uri userPortalUrl, Uri venueInfoUrl, + boolean isSessionExtendable, long byteLimit, long expiryTimeMillis, boolean captive) { + mRefreshTimeMillis = refreshTimeMillis; + mUserPortalUrl = userPortalUrl; + mVenueInfoUrl = venueInfoUrl; + mIsSessionExtendable = isSessionExtendable; + mByteLimit = byteLimit; + mExpiryTimeMillis = expiryTimeMillis; + mCaptive = captive; + } + + private CaptivePortalData(Parcel p) { + this(p.readLong(), p.readParcelable(null), p.readParcelable(null), p.readBoolean(), + p.readLong(), p.readLong(), p.readBoolean()); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeLong(mRefreshTimeMillis); + dest.writeParcelable(mUserPortalUrl, 0); + dest.writeParcelable(mVenueInfoUrl, 0); + dest.writeBoolean(mIsSessionExtendable); + dest.writeLong(mByteLimit); + dest.writeLong(mExpiryTimeMillis); + dest.writeBoolean(mCaptive); + } + + /** + * A builder to create new {@link CaptivePortalData}. + */ + public static class Builder { + private long mRefreshTime; + private Uri mUserPortalUrl; + private Uri mVenueInfoUrl; + private boolean mIsSessionExtendable; + private long mBytesRemaining = -1; + private long mExpiryTime = -1; + private boolean mCaptive; + + /** + * Create an empty builder. + */ + public Builder() {} + + /** + * Create a builder copying all data from existing {@link CaptivePortalData}. + */ + public Builder(@Nullable CaptivePortalData data) { + if (data == null) return; + setRefreshTime(data.mRefreshTimeMillis) + .setUserPortalUrl(data.mUserPortalUrl) + .setVenueInfoUrl(data.mVenueInfoUrl) + .setSessionExtendable(data.mIsSessionExtendable) + .setBytesRemaining(data.mByteLimit) + .setExpiryTime(data.mExpiryTimeMillis) + .setCaptive(data.mCaptive); + } + + /** + * Set the time at which data was last refreshed, as per {@link System#currentTimeMillis()}. + */ + @NonNull + public Builder setRefreshTime(long refreshTime) { + mRefreshTime = refreshTime; + return this; + } + + /** + * Set the URL to be used for users to login to the portal, if captive. + */ + @NonNull + public Builder setUserPortalUrl(@Nullable Uri userPortalUrl) { + mUserPortalUrl = userPortalUrl; + return this; + } + + /** + * Set the URL that can be used by users to view information about the network venue. + */ + @NonNull + public Builder setVenueInfoUrl(@Nullable Uri venueInfoUrl) { + mVenueInfoUrl = venueInfoUrl; + return this; + } + + /** + * Set whether the portal supports extending a user session on the portal URL page. + */ + @NonNull + public Builder setSessionExtendable(boolean sessionExtendable) { + mIsSessionExtendable = sessionExtendable; + return this; + } + + /** + * Set the number of bytes remaining on the network before the portal closes. + */ + @NonNull + public Builder setBytesRemaining(long bytesRemaining) { + mBytesRemaining = bytesRemaining; + return this; + } + + /** + * Set the time at the session will expire, as per {@link System#currentTimeMillis()}. + */ + @NonNull + public Builder setExpiryTime(long expiryTime) { + mExpiryTime = expiryTime; + return this; + } + + /** + * Set whether the network is captive (portal closed). + */ + @NonNull + public Builder setCaptive(boolean captive) { + mCaptive = captive; + return this; + } + + /** + * Create a new {@link CaptivePortalData}. + */ + @NonNull + public CaptivePortalData build() { + return new CaptivePortalData(mRefreshTime, mUserPortalUrl, mVenueInfoUrl, + mIsSessionExtendable, mBytesRemaining, mExpiryTime, mCaptive); + } + } + + /** + * Get the time at which data was last refreshed, as per {@link System#currentTimeMillis()}. + */ + public long getRefreshTimeMillis() { + return mRefreshTimeMillis; + } + + /** + * Get the URL to be used for users to login to the portal, or extend their session if + * {@link #isSessionExtendable()} is true. + */ + @Nullable + public Uri getUserPortalUrl() { + return mUserPortalUrl; + } + + /** + * Get the URL that can be used by users to view information about the network venue. + */ + @Nullable + public Uri getVenueInfoUrl() { + return mVenueInfoUrl; + } + + /** + * Indicates whether the user portal URL can be used to extend sessions, when the user is logged + * in and the session has a time or byte limit. + */ + public boolean isSessionExtendable() { + return mIsSessionExtendable; + } + + /** + * Get the remaining bytes on the captive portal session, at the time {@link CaptivePortalData} + * was refreshed. This may be different from the limit currently enforced by the portal. + * @return The byte limit, or -1 if not set. + */ + public long getByteLimit() { + return mByteLimit; + } + + /** + * Get the time at the session will expire, as per {@link System#currentTimeMillis()}. + * @return The expiry time, or -1 if unset. + */ + public long getExpiryTimeMillis() { + return mExpiryTimeMillis; + } + + /** + * Get whether the network is captive (portal closed). + */ + public boolean isCaptive() { + return mCaptive; + } + + @NonNull + public static final Creator<CaptivePortalData> CREATOR = new Creator<CaptivePortalData>() { + @Override + public CaptivePortalData createFromParcel(Parcel source) { + return new CaptivePortalData(source); + } + + @Override + public CaptivePortalData[] newArray(int size) { + return new CaptivePortalData[size]; + } + }; + + @Override + public int hashCode() { + return Objects.hash(mRefreshTimeMillis, mUserPortalUrl, mVenueInfoUrl, + mIsSessionExtendable, mByteLimit, mExpiryTimeMillis, mCaptive); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof CaptivePortalData)) return false; + final CaptivePortalData other = (CaptivePortalData) obj; + return mRefreshTimeMillis == other.mRefreshTimeMillis + && Objects.equals(mUserPortalUrl, other.mUserPortalUrl) + && Objects.equals(mVenueInfoUrl, other.mVenueInfoUrl) + && mIsSessionExtendable == other.mIsSessionExtendable + && mByteLimit == other.mByteLimit + && mExpiryTimeMillis == other.mExpiryTimeMillis + && mCaptive == other.mCaptive; + } + + @Override + public String toString() { + return "CaptivePortalData {" + + "refreshTime: " + mRefreshTimeMillis + + ", userPortalUrl: " + mUserPortalUrl + + ", venueInfoUrl: " + mVenueInfoUrl + + ", isSessionExtendable: " + mIsSessionExtendable + + ", byteLimit: " + mByteLimit + + ", expiryTime: " + mExpiryTimeMillis + + ", captive: " + mCaptive + + "}"; + } +} diff --git a/core/java/android/net/ConnectivityDiagnosticsManager.aidl b/core/java/android/net/ConnectivityDiagnosticsManager.aidl new file mode 100644 index 000000000000..82ba0ca113c5 --- /dev/null +++ b/core/java/android/net/ConnectivityDiagnosticsManager.aidl @@ -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 android.net; + +parcelable ConnectivityDiagnosticsManager.ConnectivityReport; +parcelable ConnectivityDiagnosticsManager.DataStallReport;
\ No newline at end of file diff --git a/core/java/android/net/ConnectivityDiagnosticsManager.java b/core/java/android/net/ConnectivityDiagnosticsManager.java index 6afdb5ef1b16..a6f9b96269e1 100644 --- a/core/java/android/net/ConnectivityDiagnosticsManager.java +++ b/core/java/android/net/ConnectivityDiagnosticsManager.java @@ -18,10 +18,18 @@ package android.net; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; import android.os.PersistableBundle; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Preconditions; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Objects; import java.util.concurrent.Executor; /** @@ -47,38 +55,47 @@ import java.util.concurrent.Executor; * </ul> */ public class ConnectivityDiagnosticsManager { - public static final int DETECTION_METHOD_DNS_EVENTS = 1; - public static final int DETECTION_METHOD_TCP_METRICS = 2; + private final Context mContext; + private final IConnectivityManager mService; /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef( - prefix = {"DETECTION_METHOD_"}, - value = {DETECTION_METHOD_DNS_EVENTS, DETECTION_METHOD_TCP_METRICS}) - public @interface DetectionMethod {} + public ConnectivityDiagnosticsManager(Context context, IConnectivityManager service) { + mContext = Preconditions.checkNotNull(context, "missing context"); + mService = Preconditions.checkNotNull(service, "missing IConnectivityManager"); + } /** @hide */ - public ConnectivityDiagnosticsManager() {} + @VisibleForTesting + public static boolean persistableBundleEquals( + @Nullable PersistableBundle a, @Nullable PersistableBundle b) { + if (a == b) return true; + if (a == null || b == null) return false; + if (!Objects.equals(a.keySet(), b.keySet())) return false; + for (String key : a.keySet()) { + if (!Objects.equals(a.get(key), b.get(key))) return false; + } + return true; + } /** Class that includes connectivity information for a specific Network at a specific time. */ - public static class ConnectivityReport { + public static final class ConnectivityReport implements Parcelable { /** The Network for which this ConnectivityReport applied */ - @NonNull public final Network network; + @NonNull private final Network mNetwork; /** * The timestamp for the report. The timestamp is taken from {@link * System#currentTimeMillis}. */ - public final long reportTimestamp; + private final long mReportTimestamp; /** LinkProperties available on the Network at the reported timestamp */ - @NonNull public final LinkProperties linkProperties; + @NonNull private final LinkProperties mLinkProperties; /** NetworkCapabilities available on the Network at the reported timestamp */ - @NonNull public final NetworkCapabilities networkCapabilities; + @NonNull private final NetworkCapabilities mNetworkCapabilities; /** PersistableBundle that may contain additional info about the report */ - @NonNull public final PersistableBundle additionalInfo; + @NonNull private final PersistableBundle mAdditionalInfo; /** * Constructor for ConnectivityReport. @@ -101,30 +118,148 @@ public class ConnectivityDiagnosticsManager { @NonNull LinkProperties linkProperties, @NonNull NetworkCapabilities networkCapabilities, @NonNull PersistableBundle additionalInfo) { - this.network = network; - this.reportTimestamp = reportTimestamp; - this.linkProperties = linkProperties; - this.networkCapabilities = networkCapabilities; - this.additionalInfo = additionalInfo; + mNetwork = network; + mReportTimestamp = reportTimestamp; + mLinkProperties = linkProperties; + mNetworkCapabilities = networkCapabilities; + mAdditionalInfo = additionalInfo; + } + + /** + * Returns the Network for this ConnectivityReport. + * + * @return The Network for which this ConnectivityReport applied + */ + @NonNull + public Network getNetwork() { + return mNetwork; + } + + /** + * Returns the epoch timestamp (milliseconds) for when this report was taken. + * + * @return The timestamp for the report. Taken from {@link System#currentTimeMillis}. + */ + public long getReportTimestamp() { + return mReportTimestamp; + } + + /** + * Returns the LinkProperties available when this report was taken. + * + * @return LinkProperties available on the Network at the reported timestamp + */ + @NonNull + public LinkProperties getLinkProperties() { + return new LinkProperties(mLinkProperties); } + + /** + * Returns the NetworkCapabilities when this report was taken. + * + * @return NetworkCapabilities available on the Network at the reported timestamp + */ + @NonNull + public NetworkCapabilities getNetworkCapabilities() { + return new NetworkCapabilities(mNetworkCapabilities); + } + + /** + * Returns a PersistableBundle with additional info for this report. + * + * @return PersistableBundle that may contain additional info about the report + */ + @NonNull + public PersistableBundle getAdditionalInfo() { + return new PersistableBundle(mAdditionalInfo); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (!(o instanceof ConnectivityReport)) return false; + final ConnectivityReport that = (ConnectivityReport) o; + + // PersistableBundle is optimized to avoid unparcelling data unless fields are + // referenced. Because of this, use {@link ConnectivityDiagnosticsManager#equals} over + // {@link PersistableBundle#kindofEquals}. + return mReportTimestamp == that.mReportTimestamp + && mNetwork.equals(that.mNetwork) + && mLinkProperties.equals(that.mLinkProperties) + && mNetworkCapabilities.equals(that.mNetworkCapabilities) + && persistableBundleEquals(mAdditionalInfo, that.mAdditionalInfo); + } + + @Override + public int hashCode() { + return Objects.hash( + mNetwork, + mReportTimestamp, + mLinkProperties, + mNetworkCapabilities, + mAdditionalInfo); + } + + /** {@inheritDoc} */ + @Override + public int describeContents() { + return 0; + } + + /** {@inheritDoc} */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeParcelable(mNetwork, flags); + dest.writeLong(mReportTimestamp); + dest.writeParcelable(mLinkProperties, flags); + dest.writeParcelable(mNetworkCapabilities, flags); + dest.writeParcelable(mAdditionalInfo, flags); + } + + /** Implement the Parcelable interface */ + public static final @NonNull Creator<ConnectivityReport> CREATOR = + new Creator<>() { + public ConnectivityReport createFromParcel(Parcel in) { + return new ConnectivityReport( + in.readParcelable(null), + in.readLong(), + in.readParcelable(null), + in.readParcelable(null), + in.readParcelable(null)); + } + + public ConnectivityReport[] newArray(int size) { + return new ConnectivityReport[size]; + } + }; } /** Class that includes information for a suspected data stall on a specific Network */ - public static class DataStallReport { + public static final class DataStallReport implements Parcelable { + public static final int DETECTION_METHOD_DNS_EVENTS = 1; + public static final int DETECTION_METHOD_TCP_METRICS = 2; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef( + prefix = {"DETECTION_METHOD_"}, + value = {DETECTION_METHOD_DNS_EVENTS, DETECTION_METHOD_TCP_METRICS}) + public @interface DetectionMethod {} + /** The Network for which this DataStallReport applied */ - @NonNull public final Network network; + @NonNull private final Network mNetwork; /** * The timestamp for the report. The timestamp is taken from {@link * System#currentTimeMillis}. */ - public final long reportTimestamp; + private long mReportTimestamp; /** The detection method used to identify the suspected data stall */ - @DetectionMethod public final int detectionMethod; + @DetectionMethod private final int mDetectionMethod; /** PersistableBundle that may contain additional information on the suspected data stall */ - @NonNull public final PersistableBundle stallDetails; + @NonNull private final PersistableBundle mStallDetails; /** * Constructor for DataStallReport. @@ -143,11 +278,101 @@ public class ConnectivityDiagnosticsManager { long reportTimestamp, @DetectionMethod int detectionMethod, @NonNull PersistableBundle stallDetails) { - this.network = network; - this.reportTimestamp = reportTimestamp; - this.detectionMethod = detectionMethod; - this.stallDetails = stallDetails; + mNetwork = network; + mReportTimestamp = reportTimestamp; + mDetectionMethod = detectionMethod; + mStallDetails = stallDetails; + } + + /** + * Returns the Network for this DataStallReport. + * + * @return The Network for which this DataStallReport applied + */ + @NonNull + public Network getNetwork() { + return mNetwork; + } + + /** + * Returns the epoch timestamp (milliseconds) for when this report was taken. + * + * @return The timestamp for the report. Taken from {@link System#currentTimeMillis}. + */ + public long getReportTimestamp() { + return mReportTimestamp; + } + + /** + * Returns the detection method used to identify this suspected data stall. + * + * @return The detection method used to identify the suspected data stall + */ + public int getDetectionMethod() { + return mDetectionMethod; + } + + /** + * Returns a PersistableBundle with additional info for this report. + * + * @return PersistableBundle that may contain additional information on the suspected data + * stall + */ + @NonNull + public PersistableBundle getStallDetails() { + return new PersistableBundle(mStallDetails); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (!(o instanceof DataStallReport)) return false; + final DataStallReport that = (DataStallReport) o; + + // PersistableBundle is optimized to avoid unparcelling data unless fields are + // referenced. Because of this, use {@link ConnectivityDiagnosticsManager#equals} over + // {@link PersistableBundle#kindofEquals}. + return mReportTimestamp == that.mReportTimestamp + && mDetectionMethod == that.mDetectionMethod + && mNetwork.equals(that.mNetwork) + && persistableBundleEquals(mStallDetails, that.mStallDetails); + } + + @Override + public int hashCode() { + return Objects.hash(mNetwork, mReportTimestamp, mDetectionMethod, mStallDetails); + } + + /** {@inheritDoc} */ + @Override + public int describeContents() { + return 0; } + + /** {@inheritDoc} */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeParcelable(mNetwork, flags); + dest.writeLong(mReportTimestamp); + dest.writeInt(mDetectionMethod); + dest.writeParcelable(mStallDetails, flags); + } + + /** Implement the Parcelable interface */ + public static final @NonNull Creator<DataStallReport> CREATOR = + new Creator<DataStallReport>() { + public DataStallReport createFromParcel(Parcel in) { + return new DataStallReport( + in.readParcelable(null), + in.readLong(), + in.readInt(), + in.readParcelable(null)); + } + + public DataStallReport[] newArray(int size) { + return new DataStallReport[size]; + } + }; } /** diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 8ba3131a83f1..753e754602d1 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -33,6 +33,7 @@ import android.content.Context; import android.content.Intent; import android.net.IpSecManager.UdpEncapsulationSocket; import android.net.SocketKeepalive.Callback; +import android.net.TetheringManager.TetheringEventCallback; import android.os.Binder; import android.os.Build; import android.os.Build.VERSION_CODES; @@ -58,6 +59,7 @@ import android.util.ArrayMap; import android.util.Log; import android.util.SparseIntArray; +import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; import com.android.internal.util.Protocol; @@ -75,6 +77,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -484,34 +487,35 @@ public class ConnectivityManager { * enable if any. * @hide */ - public static final String EXTRA_ADD_TETHER_TYPE = TetheringManager.EXTRA_ADD_TETHER_TYPE; + public static final String EXTRA_ADD_TETHER_TYPE = TetheringConstants.EXTRA_ADD_TETHER_TYPE; /** * Extra used for communicating with the TetherService. Includes the type of tethering for * which to cancel provisioning. * @hide */ - public static final String EXTRA_REM_TETHER_TYPE = TetheringManager.EXTRA_REM_TETHER_TYPE; + public static final String EXTRA_REM_TETHER_TYPE = TetheringConstants.EXTRA_REM_TETHER_TYPE; /** * Extra used for communicating with the TetherService. True to schedule a recheck of tether * provisioning. * @hide */ - public static final String EXTRA_SET_ALARM = TetheringManager.EXTRA_SET_ALARM; + public static final String EXTRA_SET_ALARM = TetheringConstants.EXTRA_SET_ALARM; /** * Tells the TetherService to run a provision check now. * @hide */ - public static final String EXTRA_RUN_PROVISION = TetheringManager.EXTRA_RUN_PROVISION; + public static final String EXTRA_RUN_PROVISION = TetheringConstants.EXTRA_RUN_PROVISION; /** * Extra used for communicating with the TetherService. Contains the {@link ResultReceiver} * which will receive provisioning results. Can be left empty. * @hide */ - public static final String EXTRA_PROVISION_CALLBACK = TetheringManager.EXTRA_PROVISION_CALLBACK; + public static final String EXTRA_PROVISION_CALLBACK = + TetheringConstants.EXTRA_PROVISION_CALLBACK; /** * The absence of a connection type. @@ -2368,10 +2372,12 @@ public class ConnectivityManager { * * @return an array of 0 or more Strings of tetherable interface names. * + * @deprecated Use {@link TetheringEventCallback#onTetherableInterfacesChanged(List)} instead. * {@hide} */ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) @UnsupportedAppUsage + @Deprecated public String[] getTetherableIfaces() { return getTetheringManager().getTetherableIfaces(); } @@ -2381,10 +2387,12 @@ public class ConnectivityManager { * * @return an array of 0 or more String of currently tethered interface names. * + * @deprecated Use {@link TetheringEventCallback#onTetherableInterfacesChanged(List)} instead. * {@hide} */ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) @UnsupportedAppUsage + @Deprecated public String[] getTetheredIfaces() { return getTetheringManager().getTetheredIfaces(); } @@ -2400,10 +2408,12 @@ public class ConnectivityManager { * @return an array of 0 or more String indicating the interface names * which failed to tether. * + * @deprecated Use {@link TetheringEventCallback#onError(String, int)} instead. * {@hide} */ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) @UnsupportedAppUsage + @Deprecated public String[] getTetheringErroredIfaces() { return getTetheringManager().getTetheringErroredIfaces(); } @@ -2412,9 +2422,11 @@ public class ConnectivityManager { * Get the set of tethered dhcp ranges. * * @return an array of 0 or more {@code String} of tethered dhcp ranges. + * @deprecated This API just return the default value which is not used in DhcpServer. * {@hide} */ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) + @Deprecated public String[] getTetheredDhcpRanges() { return getTetheringManager().getTetheredDhcpRanges(); } @@ -2467,6 +2479,7 @@ public class ConnectivityManager { * {@hide} */ @UnsupportedAppUsage + @Deprecated public int untether(String iface) { return getTetheringManager().untether(iface); } @@ -2487,6 +2500,7 @@ public class ConnectivityManager { * * @return a boolean - {@code true} indicating Tethering is supported. * + * @deprecated Use {@link TetheringEventCallback#onTetheringSupported(boolean)} instead. * {@hide} */ @SystemApi @@ -2573,9 +2587,12 @@ public class ConnectivityManager { * {@link ConnectivityManager.TETHERING_WIFI}, * {@link ConnectivityManager.TETHERING_USB}, or * {@link ConnectivityManager.TETHERING_BLUETOOTH}. + * + * @deprecated Use {@link TetheringManager#stopTethering} instead. * @hide */ @SystemApi + @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void stopTethering(int type) { getTetheringManager().stopTethering(type); @@ -2585,9 +2602,11 @@ public class ConnectivityManager { * Callback for use with {@link registerTetheringEventCallback} to find out tethering * upstream status. * - *@hide + * @deprecated Use {@line TetheringManager#OnTetheringEventCallback} instead. + * @hide */ @SystemApi + @Deprecated public abstract static class OnTetheringEventCallback { /** @@ -2600,6 +2619,10 @@ public class ConnectivityManager { public void onUpstreamChanged(@Nullable Network network) {} } + @GuardedBy("mTetheringEventCallbacks") + private final ArrayMap<OnTetheringEventCallback, TetheringEventCallback> + mTetheringEventCallbacks = new ArrayMap<>(); + /** * Start listening to tethering change events. Any new added callback will receive the last * tethering status right away. If callback is registered when tethering has no upstream or @@ -2608,16 +2631,30 @@ public class ConnectivityManager { * * @param executor the executor on which callback will be invoked. * @param callback the callback to be called when tethering has change events. + * + * @deprecated Use {@line TetheringManager#registerTetheringEventCallback} instead. * @hide */ @SystemApi + @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback( @NonNull @CallbackExecutor Executor executor, @NonNull final OnTetheringEventCallback callback) { Preconditions.checkNotNull(callback, "OnTetheringEventCallback cannot be null."); - getTetheringManager().registerTetheringEventCallback(executor, callback); + final TetheringEventCallback tetherCallback = + new TetheringEventCallback() { + @Override + public void onUpstreamChanged(@Nullable Network network) { + callback.onUpstreamChanged(network); + } + }; + + synchronized (mTetheringEventCallbacks) { + mTetheringEventCallbacks.put(callback, tetherCallback); + getTetheringManager().registerTetheringEventCallback(executor, tetherCallback); + } } /** @@ -2625,13 +2662,21 @@ public class ConnectivityManager { * {@link #registerTetheringEventCallback}. * * @param callback previously registered callback. + * + * @deprecated Use {@link TetheringManager#unregisterTetheringEventCallback} instead. * @hide */ @SystemApi + @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void unregisterTetheringEventCallback( @NonNull final OnTetheringEventCallback callback) { - getTetheringManager().unregisterTetheringEventCallback(callback); + Objects.requireNonNull(callback, "The callback must be non-null"); + synchronized (mTetheringEventCallbacks) { + final TetheringEventCallback tetherCallback = + mTetheringEventCallbacks.remove(callback); + getTetheringManager().unregisterTetheringEventCallback(tetherCallback); + } } @@ -2643,10 +2688,12 @@ public class ConnectivityManager { * @return an array of 0 or more regular expression Strings defining * what interfaces are considered tetherable usb interfaces. * + * @deprecated Use {@link TetheringEventCallback#onTetherableInterfaceRegexpsChanged} instead. * {@hide} */ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) @UnsupportedAppUsage + @Deprecated public String[] getTetherableUsbRegexs() { return getTetheringManager().getTetherableUsbRegexs(); } @@ -2659,10 +2706,12 @@ public class ConnectivityManager { * @return an array of 0 or more regular expression Strings defining * what interfaces are considered tetherable wifi interfaces. * + * @deprecated Use {@link TetheringEventCallback#onTetherableInterfaceRegexpsChanged} instead. * {@hide} */ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) @UnsupportedAppUsage + @Deprecated public String[] getTetherableWifiRegexs() { return getTetheringManager().getTetherableWifiRegexs(); } @@ -2675,10 +2724,13 @@ public class ConnectivityManager { * @return an array of 0 or more regular expression Strings defining * what interfaces are considered tetherable bluetooth interfaces. * + * @deprecated Use {@link TetheringEventCallback#onTetherableInterfaceRegexpsChanged( + *TetheringManager.TetheringInterfaceRegexps)} instead. * {@hide} */ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) @UnsupportedAppUsage + @Deprecated public String[] getTetherableBluetoothRegexs() { return getTetheringManager().getTetherableBluetoothRegexs(); } @@ -2705,37 +2757,104 @@ public class ConnectivityManager { return getTetheringManager().setUsbTethering(enable); } - /** {@hide} */ + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_NO_ERROR}. + * {@hide} + */ @SystemApi - public static final int TETHER_ERROR_NO_ERROR = 0; - /** {@hide} */ - public static final int TETHER_ERROR_UNKNOWN_IFACE = 1; - /** {@hide} */ - public static final int TETHER_ERROR_SERVICE_UNAVAIL = 2; - /** {@hide} */ - public static final int TETHER_ERROR_UNSUPPORTED = 3; - /** {@hide} */ - public static final int TETHER_ERROR_UNAVAIL_IFACE = 4; - /** {@hide} */ - public static final int TETHER_ERROR_MASTER_ERROR = 5; - /** {@hide} */ - public static final int TETHER_ERROR_TETHER_IFACE_ERROR = 6; - /** {@hide} */ - public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = 7; - /** {@hide} */ - public static final int TETHER_ERROR_ENABLE_NAT_ERROR = 8; - /** {@hide} */ - public static final int TETHER_ERROR_DISABLE_NAT_ERROR = 9; - /** {@hide} */ - public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10; - /** {@hide} */ + @Deprecated + public static final int TETHER_ERROR_NO_ERROR = TetheringManager.TETHER_ERROR_NO_ERROR; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_UNKNOWN_IFACE}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_UNKNOWN_IFACE = + TetheringManager.TETHER_ERROR_UNKNOWN_IFACE; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_SERVICE_UNAVAIL}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_SERVICE_UNAVAIL = + TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_UNSUPPORTED}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_UNSUPPORTED = TetheringManager.TETHER_ERROR_UNSUPPORTED; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_UNAVAIL_IFACE}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_UNAVAIL_IFACE = + TetheringManager.TETHER_ERROR_UNAVAIL_IFACE; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_MASTER_ERROR}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_MASTER_ERROR = TetheringManager.TETHER_ERROR_MASTER_ERROR; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_TETHER_IFACE_ERROR}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_TETHER_IFACE_ERROR = + TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_UNTETHER_IFACE_ERROR}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = + TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_ENABLE_NAT_ERROR}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_ENABLE_NAT_ERROR = + TetheringManager.TETHER_ERROR_ENABLE_NAT_ERROR; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_DISABLE_NAT_ERROR}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_DISABLE_NAT_ERROR = + TetheringManager.TETHER_ERROR_DISABLE_NAT_ERROR; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_IFACE_CFG_ERROR}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_IFACE_CFG_ERROR = + TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_PROVISION_FAILED}. + * {@hide} + */ @SystemApi - public static final int TETHER_ERROR_PROVISION_FAILED = 11; - /** {@hide} */ - public static final int TETHER_ERROR_DHCPSERVER_ERROR = 12; - /** {@hide} */ + @Deprecated + public static final int TETHER_ERROR_PROVISION_FAILED = + TetheringManager.TETHER_ERROR_PROVISION_FAILED; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_DHCPSERVER_ERROR}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_DHCPSERVER_ERROR = + TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_ENTITLEMENT_UNKNOWN}. + * {@hide} + */ @SystemApi - public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13; + @Deprecated + public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = + TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN; /** * Get a more detailed error code after a Tethering or Untethering @@ -2745,10 +2864,12 @@ public class ConnectivityManager { * @return error The error code of the last error tethering or untethering the named * interface * + * @deprecated Use {@link TetheringEventCallback#onError(String, int)} instead. * {@hide} */ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) @UnsupportedAppUsage + @Deprecated public int getLastTetherError(String iface) { return getTetheringManager().getLastTetherError(iface); } @@ -2766,9 +2887,12 @@ public class ConnectivityManager { /** * Callback for use with {@link #getLatestTetheringEntitlementResult} to find out whether * entitlement succeeded. + * + * @deprecated Use {@link TetheringManager#OnTetheringEntitlementResultListener} instead. * @hide */ @SystemApi + @Deprecated public interface OnTetheringEntitlementResultListener { /** * Called to notify entitlement result. @@ -2798,9 +2922,11 @@ public class ConnectivityManager { * @param listener an {@link OnTetheringEntitlementResultListener} which will be called to * notify the caller of the result of entitlement check. The listener may be called zero * or one time. + * @deprecated Use {@link TetheringManager#requestLatestTetheringEntitlementResult} instead. * {@hide} */ @SystemApi + @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void getLatestTetheringEntitlementResult(int type, boolean showEntitlementUi, @NonNull @CallbackExecutor Executor executor, diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java index be8e561fd044..e83f5e4e810d 100644 --- a/core/java/android/net/LinkProperties.java +++ b/core/java/android/net/LinkProperties.java @@ -72,6 +72,14 @@ public final class LinkProperties implements Parcelable { private String mTcpBufferSizes; private IpPrefix mNat64Prefix; private boolean mWakeOnLanSupported; + private Uri mCaptivePortalApiUrl; + private CaptivePortalData mCaptivePortalData; + + /** + * Indicates whether parceling should preserve fields that are set based on permissions of + * the process receiving the {@link LinkProperties}. + */ + private final transient boolean mParcelSensitiveFields; private static final int MIN_MTU = 68; private static final int MIN_MTU_V6 = 1280; @@ -146,6 +154,7 @@ public final class LinkProperties implements Parcelable { * Constructs a new {@code LinkProperties} with default values. */ public LinkProperties() { + mParcelSensitiveFields = false; } /** @@ -154,26 +163,32 @@ public final class LinkProperties implements Parcelable { @SystemApi @TestApi public LinkProperties(@Nullable LinkProperties source) { - if (source != null) { - mIfaceName = source.mIfaceName; - mLinkAddresses.addAll(source.mLinkAddresses); - mDnses.addAll(source.mDnses); - mValidatedPrivateDnses.addAll(source.mValidatedPrivateDnses); - mUsePrivateDns = source.mUsePrivateDns; - mPrivateDnsServerName = source.mPrivateDnsServerName; - mPcscfs.addAll(source.mPcscfs); - mDomains = source.mDomains; - mRoutes.addAll(source.mRoutes); - mHttpProxy = (source.mHttpProxy == null) ? null : new ProxyInfo(source.mHttpProxy); - for (LinkProperties l: source.mStackedLinks.values()) { - addStackedLink(l); - } - setMtu(source.mMtu); - setDhcpServerAddress(source.getDhcpServerAddress()); - mTcpBufferSizes = source.mTcpBufferSizes; - mNat64Prefix = source.mNat64Prefix; - mWakeOnLanSupported = source.mWakeOnLanSupported; + this(source, false /* parcelSensitiveFields */); + } + + private LinkProperties(@Nullable LinkProperties source, boolean parcelSensitiveFields) { + mParcelSensitiveFields = parcelSensitiveFields; + if (source == null) return; + mIfaceName = source.mIfaceName; + mLinkAddresses.addAll(source.mLinkAddresses); + mDnses.addAll(source.mDnses); + mValidatedPrivateDnses.addAll(source.mValidatedPrivateDnses); + mUsePrivateDns = source.mUsePrivateDns; + mPrivateDnsServerName = source.mPrivateDnsServerName; + mPcscfs.addAll(source.mPcscfs); + mDomains = source.mDomains; + mRoutes.addAll(source.mRoutes); + mHttpProxy = (source.mHttpProxy == null) ? null : new ProxyInfo(source.mHttpProxy); + for (LinkProperties l: source.mStackedLinks.values()) { + addStackedLink(l); } + setMtu(source.mMtu); + setDhcpServerAddress(source.getDhcpServerAddress()); + mTcpBufferSizes = source.mTcpBufferSizes; + mNat64Prefix = source.mNat64Prefix; + mWakeOnLanSupported = source.mWakeOnLanSupported; + mCaptivePortalApiUrl = source.mCaptivePortalApiUrl; + mCaptivePortalData = source.mCaptivePortalData; } /** @@ -832,6 +847,11 @@ public final class LinkProperties implements Parcelable { * Clears this object to its initial state. */ public void clear() { + if (mParcelSensitiveFields) { + throw new UnsupportedOperationException( + "Cannot clear LinkProperties when parcelSensitiveFields is set"); + } + mIfaceName = null; mLinkAddresses.clear(); mDnses.clear(); @@ -847,6 +867,8 @@ public final class LinkProperties implements Parcelable { mTcpBufferSizes = null; mNat64Prefix = null; mWakeOnLanSupported = false; + mCaptivePortalApiUrl = null; + mCaptivePortalData = null; } /** @@ -917,6 +939,14 @@ public final class LinkProperties implements Parcelable { resultJoiner.add(mDhcpServerAddress.toString()); } + if (mCaptivePortalApiUrl != null) { + resultJoiner.add("CaptivePortalApiUrl: " + mCaptivePortalApiUrl); + } + + if (mCaptivePortalData != null) { + resultJoiner.add("CaptivePortalData: " + mCaptivePortalData); + } + if (mTcpBufferSizes != null) { resultJoiner.add("TcpBufferSizes:"); resultJoiner.add(mTcpBufferSizes); @@ -1437,6 +1467,28 @@ public final class LinkProperties implements Parcelable { } /** + * Compares this {@code LinkProperties}'s CaptivePortalApiUrl against the target. + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + public boolean isIdenticalCaptivePortalApiUrl(LinkProperties target) { + return Objects.equals(mCaptivePortalApiUrl, target.mCaptivePortalApiUrl); + } + + /** + * Compares this {@code LinkProperties}'s CaptivePortalData against the target. + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + public boolean isIdenticalCaptivePortalData(LinkProperties target) { + return Objects.equals(mCaptivePortalData, target.mCaptivePortalData); + } + + /** * Set whether the network interface supports WakeOnLAN * * @param supported WakeOnLAN supported value @@ -1457,6 +1509,73 @@ public final class LinkProperties implements Parcelable { } /** + * Set the URL of the captive portal API endpoint to get more information about the network. + * @hide + */ + @SystemApi + @TestApi + public void setCaptivePortalApiUrl(@Nullable Uri url) { + mCaptivePortalApiUrl = url; + } + + /** + * Get the URL of the captive portal API endpoint to get more information about the network. + * + * <p>This is null unless the application has + * {@link android.Manifest.permission.NETWORK_SETTINGS} or + * {@link NetworkStack#PERMISSION_MAINLINE_NETWORK_STACK} permissions, and the network provided + * the URL. + * @hide + */ + @SystemApi + @TestApi + @Nullable + public Uri getCaptivePortalApiUrl() { + return mCaptivePortalApiUrl; + } + + /** + * Set the CaptivePortalData obtained from the captive portal API (RFC7710bis). + * @hide + */ + @SystemApi + @TestApi + public void setCaptivePortalData(@Nullable CaptivePortalData data) { + mCaptivePortalData = data; + } + + /** + * Get the CaptivePortalData obtained from the captive portal API (RFC7710bis). + * + * <p>This is null unless the application has + * {@link android.Manifest.permission.NETWORK_SETTINGS} or + * {@link NetworkStack#PERMISSION_MAINLINE_NETWORK_STACK} permissions. + * @hide + */ + @SystemApi + @TestApi + @Nullable + public CaptivePortalData getCaptivePortalData() { + return mCaptivePortalData; + } + + /** + * Create a copy of this {@link LinkProperties} that will preserve fields that were set + * based on the permissions of the process that received this {@link LinkProperties}. + * + * <p>By default {@link LinkProperties} does not preserve such fields during parceling, as + * they should not be shared outside of the process that receives them without appropriate + * checks. + * @hide + */ + @SystemApi + @TestApi + @NonNull + public LinkProperties makeSensitiveFieldsParcelingCopy() { + return new LinkProperties(this, true /* parcelSensitiveFields */); + } + + /** * Compares this {@code LinkProperties} instance against the target * LinkProperties in {@code obj}. Two LinkPropertieses are equal if * all their fields are equal in values. @@ -1495,7 +1614,9 @@ public final class LinkProperties implements Parcelable { && isIdenticalMtu(target) && isIdenticalTcpBufferSizes(target) && isIdenticalNat64Prefix(target) - && isIdenticalWakeOnLan(target); + && isIdenticalWakeOnLan(target) + && isIdenticalCaptivePortalApiUrl(target) + && isIdenticalCaptivePortalData(target); } /** @@ -1593,7 +1714,8 @@ public final class LinkProperties implements Parcelable { + mPcscfs.size() * 67 + ((null == mPrivateDnsServerName) ? 0 : mPrivateDnsServerName.hashCode()) + Objects.hash(mNat64Prefix) - + (mWakeOnLanSupported ? 71 : 0); + + (mWakeOnLanSupported ? 71 : 0) + + Objects.hash(mCaptivePortalApiUrl, mCaptivePortalData); } /** @@ -1632,6 +1754,8 @@ public final class LinkProperties implements Parcelable { dest.writeList(stackedLinks); dest.writeBoolean(mWakeOnLanSupported); + dest.writeParcelable(mParcelSensitiveFields ? mCaptivePortalApiUrl : null, 0); + dest.writeParcelable(mParcelSensitiveFields ? mCaptivePortalData : null, 0); } private static void writeAddresses(@NonNull Parcel dest, @NonNull List<InetAddress> list) { @@ -1723,6 +1847,9 @@ public final class LinkProperties implements Parcelable { netProp.addStackedLink(stackedLink); } netProp.setWakeOnLanSupported(in.readBoolean()); + + netProp.setCaptivePortalApiUrl(in.readParcelable(null)); + netProp.setCaptivePortalData(in.readParcelable(null)); return netProp; } diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java index 7cc78f7389c2..7cc569a42b0b 100644 --- a/core/java/android/net/NetworkAgent.java +++ b/core/java/android/net/NetworkAgent.java @@ -304,7 +304,10 @@ public abstract class NetworkAgent { private static NetworkInfo getLegacyNetworkInfo(final NetworkAgentConfig config) { // The subtype can be changed with (TODO) setLegacySubtype, but it starts // with the type and an empty description. - return new NetworkInfo(config.legacyType, config.legacyType, config.legacyTypeName, ""); + final NetworkInfo ni = new NetworkInfo(config.legacyType, config.legacyType, + config.legacyTypeName, ""); + ni.setIsAvailable(true); + return ni; } /** diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index 739e8178e68e..738070b09264 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -1283,6 +1283,7 @@ public final class NetworkCapabilities implements Parcelable { * Gets the SSID of this network, or null if none or unknown. * @hide */ + @SystemApi public @Nullable String getSSID() { return mSSID; } diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java index d0c536316527..08fe159b276e 100644 --- a/core/java/android/net/NetworkInfo.java +++ b/core/java/android/net/NetworkInfo.java @@ -17,9 +17,11 @@ package android.net; import android.annotation.NonNull; +import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; +import android.telephony.Annotation.NetworkType; import com.android.internal.annotations.VisibleForTesting; @@ -150,10 +152,19 @@ public class NetworkInfo implements Parcelable { private boolean mIsRoaming; /** - * @hide + * Create a new instance of NetworkInfo. + * + * This may be useful for apps to write unit tests. + * + * @param type the legacy type of the network, as one of the ConnectivityManager.TYPE_* + * constants. + * @param subtype the subtype if applicable, as one of the TelephonyManager.NETWORK_TYPE_* + * constants. + * @param typeName a human-readable string for the network type, or an empty string or null. + * @param subtypeName a human-readable string for the subtype, or an empty string or null. */ - @UnsupportedAppUsage - public NetworkInfo(int type, int subtype, String typeName, String subtypeName) { + public NetworkInfo(int type, @NetworkType int subtype, + @Nullable String typeName, @Nullable String subtypeName) { if (!ConnectivityManager.isNetworkTypeValid(type) && type != ConnectivityManager.TYPE_NONE) { throw new IllegalArgumentException("Invalid network type: " + type); @@ -462,17 +473,19 @@ public class NetworkInfo implements Parcelable { /** * Sets the fine-grained state of the network. + * + * This is only useful for testing. + * * @param detailedState the {@link DetailedState}. * @param reason a {@code String} indicating the reason for the state change, * if one was supplied. May be {@code null}. * @param extraInfo an optional {@code String} providing addditional network state * information passed up from the lower networking layers. * @deprecated Use {@link NetworkCapabilities} instead. - * @hide */ @Deprecated - @UnsupportedAppUsage - public void setDetailedState(DetailedState detailedState, String reason, String extraInfo) { + public void setDetailedState(@NonNull DetailedState detailedState, @Nullable String reason, + @Nullable String extraInfo) { synchronized (this) { this.mDetailedState = detailedState; this.mState = stateMap.get(detailedState); diff --git a/core/java/android/net/NetworkKey.java b/core/java/android/net/NetworkKey.java index 47c08a450fc4..4469d7de28fb 100644 --- a/core/java/android/net/NetworkKey.java +++ b/core/java/android/net/NetworkKey.java @@ -73,13 +73,14 @@ public class NetworkKey implements Parcelable { /** * Constructs a new NetworkKey for the given wifi {@link ScanResult}. * - * @return A new {@link NetworkKey} instance or <code>null</code> if the given - * {@link ScanResult} instance is malformed. + * @return A new {@link NetworkKey} instance or <code>null</code> if the given + * {@link ScanResult} instance is malformed. + * @throws IllegalArgumentException */ @Nullable - public static NetworkKey createFromScanResult(@Nullable ScanResult result) { + public static NetworkKey createFromScanResult(@NonNull ScanResult result) { if (result == null) { - return null; + throw new IllegalArgumentException("ScanResult cannot be null"); } final String ssid = result.SSID; if (TextUtils.isEmpty(ssid) || ssid.equals(WifiManager.UNKNOWN_SSID)) { diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index de962f8f25e3..01800c6751fb 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -18,6 +18,10 @@ package android.net; import static android.content.pm.PackageManager.GET_SIGNATURES; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.annotation.SystemService; import android.app.ActivityManager; import android.compat.annotation.UnsupportedAppUsage; @@ -38,24 +42,38 @@ import android.util.Range; import com.google.android.collect.Sets; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.time.ZonedDateTime; import java.util.HashSet; import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** * Manager for creating and modifying network policy rules. * - * {@hide} + * @hide */ @SystemService(Context.NETWORK_POLICY_SERVICE) +@SystemApi public class NetworkPolicyManager { /* POLICY_* are masks and can be ORed, although currently they are not.*/ - /** No specific network policy, use system default. */ + /** + * No specific network policy, use system default. + * @hide + */ public static final int POLICY_NONE = 0x0; - /** Reject network usage on metered networks when application in background. */ + /** + * Reject network usage on metered networks when application in background. + * @hide + */ public static final int POLICY_REJECT_METERED_BACKGROUND = 0x1; - /** Allow metered network use in the background even when in data usage save mode. */ + /** + * Allow metered network use in the background even when in data usage save mode. + * @hide + */ public static final int POLICY_ALLOW_METERED_BACKGROUND = 0x4; /* @@ -74,49 +92,102 @@ public class NetworkPolicyManager { * * See network-policy-restrictions.md for more info. */ - /** No specific rule was set */ + + /** + * No specific rule was set + * @hide + */ public static final int RULE_NONE = 0; - /** Allow traffic on metered networks. */ + /** + * Allow traffic on metered networks. + * @hide + */ public static final int RULE_ALLOW_METERED = 1 << 0; - /** Temporarily allow traffic on metered networks because app is on foreground. */ + /** + * Temporarily allow traffic on metered networks because app is on foreground. + * @hide + */ public static final int RULE_TEMPORARY_ALLOW_METERED = 1 << 1; - /** Reject traffic on metered networks. */ + /** + * Reject traffic on metered networks. + * @hide + */ public static final int RULE_REJECT_METERED = 1 << 2; - /** Network traffic should be allowed on all networks (metered or non-metered), although - * metered-network restrictions could still apply. */ + /** + * Network traffic should be allowed on all networks (metered or non-metered), although + * metered-network restrictions could still apply. + * @hide + */ public static final int RULE_ALLOW_ALL = 1 << 5; - /** Reject traffic on all networks. */ + /** + * Reject traffic on all networks. + * @hide + */ public static final int RULE_REJECT_ALL = 1 << 6; - /** Mask used to get the {@code RULE_xxx_METERED} rules */ + + /** + * Mask used to get the {@code RULE_xxx_METERED} rules + * @hide + */ public static final int MASK_METERED_NETWORKS = 0b00001111; - /** Mask used to get the {@code RULE_xxx_ALL} rules */ + /** + * Mask used to get the {@code RULE_xxx_ALL} rules + * @hide + */ public static final int MASK_ALL_NETWORKS = 0b11110000; + /** @hide */ public static final int FIREWALL_RULE_DEFAULT = 0; - + /** @hide */ public static final String FIREWALL_CHAIN_NAME_NONE = "none"; + /** @hide */ public static final String FIREWALL_CHAIN_NAME_DOZABLE = "dozable"; + /** @hide */ public static final String FIREWALL_CHAIN_NAME_STANDBY = "standby"; + /** @hide */ public static final String FIREWALL_CHAIN_NAME_POWERSAVE = "powersave"; private static final boolean ALLOW_PLATFORM_APP_POLICY = true; + /** @hide */ public static final int FOREGROUND_THRESHOLD_STATE = ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; /** * {@link Intent} extra that indicates which {@link NetworkTemplate} rule it * applies to. + * @hide */ public static final String EXTRA_NETWORK_TEMPLATE = "android.net.NETWORK_TEMPLATE"; - public static final int OVERRIDE_UNMETERED = 1 << 0; - public static final int OVERRIDE_CONGESTED = 1 << 1; + /** + * Mask used to check if an override value is marked as unmetered. + */ + public static final int SUBSCRIPTION_OVERRIDE_UNMETERED = 1 << 0; + + /** + * Mask used to check if an override value is marked as congested. + */ + public static final int SUBSCRIPTION_OVERRIDE_CONGESTED = 1 << 1; + + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = { "SUBSCRIPTION_OVERRIDE_" }, value = { + SUBSCRIPTION_OVERRIDE_UNMETERED, + SUBSCRIPTION_OVERRIDE_CONGESTED + }) + public @interface SubscriptionOverrideMask {} private final Context mContext; @UnsupportedAppUsage private INetworkPolicyManager mService; + private final Map<SubscriptionCallback, SubscriptionCallbackProxy> + mCallbackMap = new ConcurrentHashMap<>(); + + /** @hide */ public NetworkPolicyManager(Context context, INetworkPolicyManager service) { if (service == null) { throw new IllegalArgumentException("missing INetworkPolicyManager"); @@ -125,6 +196,7 @@ public class NetworkPolicyManager { mService = service; } + /** @hide */ @UnsupportedAppUsage public static NetworkPolicyManager from(Context context) { return (NetworkPolicyManager) context.getSystemService(Context.NETWORK_POLICY_SERVICE); @@ -135,6 +207,7 @@ public class NetworkPolicyManager { * * @param policy should be {@link #POLICY_NONE} or any combination of {@code POLICY_} flags, * although it is not validated. + * @hide */ @UnsupportedAppUsage public void setUidPolicy(int uid, int policy) { @@ -152,6 +225,7 @@ public class NetworkPolicyManager { * * @param policy should be {@link #POLICY_NONE} or any combination of {@code POLICY_} flags, * although it is not validated. + * @hide */ public void addUidPolicy(int uid, int policy) { try { @@ -168,6 +242,7 @@ public class NetworkPolicyManager { * * @param policy should be {@link #POLICY_NONE} or any combination of {@code POLICY_} flags, * although it is not validated. + * @hide */ public void removeUidPolicy(int uid, int policy) { try { @@ -177,6 +252,7 @@ public class NetworkPolicyManager { } } + /** @hide */ @UnsupportedAppUsage public int getUidPolicy(int uid) { try { @@ -186,6 +262,7 @@ public class NetworkPolicyManager { } } + /** @hide */ @UnsupportedAppUsage public int[] getUidsWithPolicy(int policy) { try { @@ -195,6 +272,7 @@ public class NetworkPolicyManager { } } + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public void registerListener(INetworkPolicyListener listener) { try { @@ -204,6 +282,7 @@ public class NetworkPolicyManager { } } + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public void unregisterListener(INetworkPolicyListener listener) { try { @@ -213,6 +292,36 @@ public class NetworkPolicyManager { } } + /** @hide */ + @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) + @SystemApi + public void registerSubscriptionCallback(@NonNull SubscriptionCallback callback) { + if (callback == null) { + throw new NullPointerException("Callback cannot be null."); + } + + final SubscriptionCallbackProxy callbackProxy = new SubscriptionCallbackProxy(callback); + if (null != mCallbackMap.putIfAbsent(callback, callbackProxy)) { + throw new IllegalArgumentException("Callback is already registered."); + } + registerListener(callbackProxy); + } + + /** @hide */ + @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) + @SystemApi + public void unregisterSubscriptionCallback(@NonNull SubscriptionCallback callback) { + if (callback == null) { + throw new NullPointerException("Callback cannot be null."); + } + + final SubscriptionCallbackProxy callbackProxy = mCallbackMap.remove(callback); + if (callbackProxy == null) return; + + unregisterListener(callbackProxy); + } + + /** @hide */ public void setNetworkPolicies(NetworkPolicy[] policies) { try { mService.setNetworkPolicies(policies); @@ -221,6 +330,7 @@ public class NetworkPolicyManager { } } + /** @hide */ @UnsupportedAppUsage public NetworkPolicy[] getNetworkPolicies() { try { @@ -230,6 +340,7 @@ public class NetworkPolicyManager { } } + /** @hide */ @UnsupportedAppUsage public void setRestrictBackground(boolean restrictBackground) { try { @@ -239,6 +350,7 @@ public class NetworkPolicyManager { } } + /** @hide */ @UnsupportedAppUsage public boolean getRestrictBackground() { try { @@ -249,6 +361,62 @@ public class NetworkPolicyManager { } /** + * Override connections to be temporarily marked as either unmetered or congested, + * along with automatic timeouts if desired. + * + * @param subId the subscriber ID this override applies to. + * @param overrideMask the bitmask that specifies which of the overrides is being + * set or cleared. + * @param overrideValue the override values to set or clear. + * @param timeoutMillis the timeout after which the requested override will + * be automatically cleared, or {@code 0} to leave in the + * requested state until explicitly cleared, or the next reboot, + * whichever happens first + * @param callingPackage the name of the package making the call. + */ + public void setSubscriptionOverride(int subId, @SubscriptionOverrideMask int overrideMask, + @SubscriptionOverrideMask int overrideValue, long timeoutMillis, + @NonNull String callingPackage) { + try { + mService.setSubscriptionOverride(subId, overrideMask, overrideValue, timeoutMillis, + callingPackage); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Set the subscription plans for a specific subscriber. + * + * @param subId the subscriber this relationship applies to. + * @param plans the list of plans. + * @param callingPackage the name of the package making the call + */ + public void setSubscriptionPlans(int subId, @NonNull SubscriptionPlan[] plans, + @NonNull String callingPackage) { + try { + mService.setSubscriptionPlans(subId, plans, callingPackage); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Get subscription plans for the given subscription id. + * + * @param subId the subscriber to get the subscription plans for. + * @param callingPackage the name of the package making the call. + */ + @NonNull + public SubscriptionPlan[] getSubscriptionPlans(int subId, @NonNull String callingPackage) { + try { + return mService.getSubscriptionPlans(subId, callingPackage); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Resets network policy settings back to factory defaults. * * @hide @@ -286,6 +454,7 @@ public class NetworkPolicyManager { /** * Check if given UID can have a {@link #setUidPolicy(int, int)} defined, * usually to protect critical system services. + * @hide */ @Deprecated public static boolean isUidValidForPolicy(Context context, int uid) { @@ -353,6 +522,7 @@ public class NetworkPolicyManager { /** * Returns true if {@param procState} is considered foreground and as such will be allowed * to access network when the device is idle or in battery saver mode. Otherwise, false. + * @hide */ public static boolean isProcStateAllowedWhileIdleOrPowerSaveMode(int procState) { return procState <= FOREGROUND_THRESHOLD_STATE; @@ -361,20 +531,68 @@ public class NetworkPolicyManager { /** * Returns true if {@param procState} is considered foreground and as such will be allowed * to access network when the device is in data saver mode. Otherwise, false. + * @hide */ public static boolean isProcStateAllowedWhileOnRestrictBackground(int procState) { return procState <= FOREGROUND_THRESHOLD_STATE; } + /** @hide */ public static String resolveNetworkId(WifiConfiguration config) { return WifiInfo.removeDoubleQuotes(config.isPasspoint() ? config.providerFriendlyName : config.SSID); } + /** @hide */ public static String resolveNetworkId(String ssid) { return WifiInfo.removeDoubleQuotes(ssid); } + /** @hide */ + @SystemApi + public static class SubscriptionCallback { + /** + * Notify clients of a new override about a given subscription. + * + * @param subId the subscriber this override applies to. + * @param overrideMask a bitmask that specifies which of the overrides is set. + * @param overrideValue a bitmask that specifies the override values. + */ + public void onSubscriptionOverride(int subId, @SubscriptionOverrideMask int overrideMask, + @SubscriptionOverrideMask int overrideValue) {} + + /** + * Notify of subscription plans change about a given subscription. + * + * @param subId the subscriber id that got subscription plans change. + * @param plans the list of subscription plans. + */ + public void onSubscriptionPlansChanged(int subId, @NonNull SubscriptionPlan[] plans) {} + } + + /** + * SubscriptionCallback proxy for SubscriptionCallback object. + * @hide + */ + public class SubscriptionCallbackProxy extends Listener { + private final SubscriptionCallback mCallback; + + SubscriptionCallbackProxy(SubscriptionCallback callback) { + mCallback = callback; + } + + @Override + public void onSubscriptionOverride(int subId, @SubscriptionOverrideMask int overrideMask, + @SubscriptionOverrideMask int overrideValue) { + mCallback.onSubscriptionOverride(subId, overrideMask, overrideValue); + } + + @Override + public void onSubscriptionPlansChanged(int subId, SubscriptionPlan[] plans) { + mCallback.onSubscriptionPlansChanged(subId, plans); + } + } + /** {@hide} */ public static class Listener extends INetworkPolicyListener.Stub { @Override public void onUidRulesChanged(int uid, int uidRules) { } diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java index c233ec0e52cf..a190c473f0a0 100644 --- a/core/java/android/net/NetworkScoreManager.java +++ b/core/java/android/net/NetworkScoreManager.java @@ -35,6 +35,7 @@ import android.util.Log; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Collection; import java.util.List; import java.util.concurrent.Executor; @@ -385,7 +386,6 @@ public class NetworkScoreManager { * * @hide */ - @SystemApi @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES) public boolean requestScores(@NonNull NetworkKey[] networks) throws SecurityException { try { @@ -396,6 +396,28 @@ public class NetworkScoreManager { } /** + * Request scoring for networks. + * + * <p> + * Note: The results (i.e scores) for these networks, when available will be provided via the + * callback registered with {@link #registerNetworkScoreCallback(int, int, Executor, + * NetworkScoreCallback)}. The calling module is responsible for registering a callback to + * receive the results before requesting new scores via this API. + * + * @return true if the request was successfully sent, or false if there is no active scorer. + * @throws SecurityException if the caller does not hold the + * {@link permission#REQUEST_NETWORK_SCORES} permission. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES) + public boolean requestScores(@NonNull Collection<NetworkKey> networks) + throws SecurityException { + return requestScores(networks.toArray(new NetworkKey[0])); + } + + /** * Register a network score cache. * * @param networkType the type of network this cache can handle. See {@link NetworkKey#type}. @@ -454,26 +476,25 @@ public class NetworkScoreManager { /** * Base class for network score cache callback. Should be extended by applications and set - * when calling {@link #registerNetworkScoreCallback(int, int, NetworkScoreCallback, - * Executor)} + * when calling {@link #registerNetworkScoreCallback(int, int, Executor, NetworkScoreCallback)}. * * @hide */ @SystemApi - public interface NetworkScoreCallback { + public abstract static class NetworkScoreCallback { /** * Called when a new set of network scores are available. * This is triggered in response when the client invokes - * {@link #requestScores(NetworkKey[])} to score a new set of networks. + * {@link #requestScores(Collection)} to score a new set of networks. * * @param networks List of {@link ScoredNetwork} containing updated scores. */ - void updateScores(@NonNull List<ScoredNetwork> networks); + public abstract void onScoresUpdated(@NonNull Collection<ScoredNetwork> networks); /** * Invokes when all the previously provided scores are no longer valid. */ - void clearScores(); + public abstract void onScoresInvalidated(); } /** @@ -492,7 +513,7 @@ public class NetworkScoreManager { public void updateScores(@NonNull List<ScoredNetwork> networks) { Binder.clearCallingIdentity(); mExecutor.execute(() -> { - mCallback.updateScores(networks); + mCallback.onScoresUpdated(networks); }); } @@ -500,7 +521,7 @@ public class NetworkScoreManager { public void clearScores() { Binder.clearCallingIdentity(); mExecutor.execute(() -> { - mCallback.clearScores(); + mCallback.onScoresInvalidated(); }); } } diff --git a/core/java/android/net/StringNetworkSpecifier.java b/core/java/android/net/StringNetworkSpecifier.java index 83dbc637fb65..6ae59716cfd8 100644 --- a/core/java/android/net/StringNetworkSpecifier.java +++ b/core/java/android/net/StringNetworkSpecifier.java @@ -17,7 +17,6 @@ package android.net; import android.annotation.NonNull; -import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; @@ -27,7 +26,6 @@ import com.android.internal.util.Preconditions; import java.util.Objects; /** @hide */ -@SystemApi public final class StringNetworkSpecifier extends NetworkSpecifier implements Parcelable { /** * Arbitrary string used to pass (additional) information to the network factory. diff --git a/core/java/android/net/Uri.aidl b/core/java/android/net/Uri.aidl index 6bd3be5e041b..b85f63bbcdd2 100644 --- a/core/java/android/net/Uri.aidl +++ b/core/java/android/net/Uri.aidl @@ -16,4 +16,4 @@ package android.net; -parcelable Uri; +@JavaOnlyStableParcelable parcelable Uri; diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl index 185693e8e6e0..3ae570081f4d 100644 --- a/core/java/android/os/IPowerManager.aidl +++ b/core/java/android/os/IPowerManager.aidl @@ -80,6 +80,14 @@ interface IPowerManager // controls whether PowerManager should doze after the screen turns off or not void setDozeAfterScreenOff(boolean on); + // returns whether ambient display is available on the device. + boolean isAmbientDisplayAvailable(); + // suppresses the current ambient display configuration and disables ambient display. + void suppressAmbientDisplay(String token, boolean suppress); + // returns whether ambient display is suppressed by the calling app with the given token. + boolean isAmbientDisplaySuppressedForToken(String token); + // returns whether ambient display is suppressed by any app with any token. + boolean isAmbientDisplaySuppressed(); // Forces the system to suspend even if there are held wakelocks. boolean forceSuspend(); diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 0414b14ae02d..6a80788c81cc 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -247,6 +247,19 @@ public final class PowerManager { */ public static final int BRIGHTNESS_DEFAULT = -1; + /** + * Brightness value for fully off in float. + * TODO: rename this to BRIGHTNES_OFF and remove the integer-based constant. + * @hide + */ + public static final float BRIGHTNESS_OFF_FLOAT = -1.0f; + + /** + * Invalid brightness value. + * @hide + */ + public static final float BRIGHTNESS_INVALID_FLOAT = Float.NaN; + // Note: Be sure to update android.os.BatteryStats and PowerManager.h // if adding or modifying user activity event constants. @@ -1870,6 +1883,77 @@ public final class PowerManager { } /** + * Returns true if ambient display is available on the device. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) + public boolean isAmbientDisplayAvailable() { + try { + return mService.isAmbientDisplayAvailable(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * If true, suppresses the current ambient display configuration and disables ambient display. + * + * <p>This method has no effect if {@link #isAmbientDisplayAvailable()} is false. + * + * @param token A persistable identifier for the ambient display suppression that is unique + * within the calling application. + * @param suppress If set to {@code true}, ambient display will be suppressed. If set to + * {@code false}, ambient display will no longer be suppressed for the given + * token. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) + public void suppressAmbientDisplay(@NonNull String token, boolean suppress) { + try { + mService.suppressAmbientDisplay(token, suppress); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns true if ambient display is suppressed by the calling app with the given + * {@code token}. + * + * <p>This method will return false if {@link #isAmbientDisplayAvailable()} is false. + * + * @param token The identifier of the ambient display suppression. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) + public boolean isAmbientDisplaySuppressedForToken(@NonNull String token) { + try { + return mService.isAmbientDisplaySuppressedForToken(token); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns true if ambient display is suppressed by <em>any</em> app with <em>any</em> token. + * + * <p>This method will return false if {@link #isAmbientDisplayAvailable()} is false. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) + public boolean isAmbientDisplaySuppressed() { + try { + return mService.isAmbientDisplaySuppressed(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Returns the reason the phone was last shutdown. Calling app must have the * {@link android.Manifest.permission#DEVICE_POWER} permission to request this information. * @return Reason for shutdown as an int, {@link #SHUTDOWN_REASON_UNKNOWN} if the file could diff --git a/core/java/android/os/TelephonyServiceManager.java b/core/java/android/os/TelephonyServiceManager.java index 064cf7d825b5..4f5f3d69b6f3 100644 --- a/core/java/android/os/TelephonyServiceManager.java +++ b/core/java/android/os/TelephonyServiceManager.java @@ -226,12 +226,4 @@ public class TelephonyServiceManager { public ServiceRegisterer getIccPhoneBookServiceRegisterer() { return new ServiceRegisterer("simphonebook"); } - - /** - * Returns {@link ServiceRegisterer} for the window service. - */ - @NonNull - public ServiceRegisterer getWindowServiceRegisterer() { - return new ServiceRegisterer(Context.WINDOW_SERVICE); - } } diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index 84ceca0c3622..091d78e88c98 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -796,6 +796,14 @@ public final class DeviceConfig { } /** + * Returns list of namespaces that can be read without READ_DEVICE_CONFIG_PERMISSION; + * @hide + */ + public static @NonNull List<String> getPublicNamespaces() { + return PUBLIC_NAMESPACES; + } + + /** * Interface for monitoring changes to properties. Implementations will receive callbacks when * properties change, including a {@link Properties} object which contains a single namespace * and all of the properties which changed for that namespace. This includes properties which diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index c7c3140f7365..00b2feba8bcd 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -63,6 +63,7 @@ import android.os.IBinder; import android.os.LocaleList; import android.os.PowerManager.AutoPowerSaveModeTriggers; import android.os.Process; +import android.os.RemoteCallback; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; @@ -2161,6 +2162,11 @@ public final class Settings { public static final String CALL_METHOD_PREFIX_KEY = "_prefix"; /** + * @hide - RemoteCallback monitor callback argument extra to the fast-path call()-based requests + */ + public static final String CALL_METHOD_MONITOR_CALLBACK_KEY = "_monitor_callback_key"; + + /** * @hide - String argument extra to the fast-path call()-based requests */ public static final String CALL_METHOD_FLAGS_KEY = "_flags"; @@ -2218,6 +2224,26 @@ public final class Settings { /** @hide - Private call() method to reset to defaults the 'configuration' table */ public static final String CALL_METHOD_LIST_CONFIG = "LIST_config"; + /** @hide - Private call() method to register monitor callback for 'configuration' table */ + public static final String CALL_METHOD_REGISTER_MONITOR_CALLBACK_CONFIG = + "REGISTER_MONITOR_CALLBACK_config"; + + /** @hide - String argument extra to the config monitor callback */ + public static final String EXTRA_MONITOR_CALLBACK_TYPE = "monitor_callback_type"; + + /** @hide - String argument extra to the config monitor callback */ + public static final String EXTRA_ACCESS_CALLBACK = "access_callback"; + + /** @hide - String argument extra to the config monitor callback */ + public static final String EXTRA_NAMESPACE_UPDATED_CALLBACK = + "namespace_updated_callback"; + + /** @hide - String argument extra to the config monitor callback */ + public static final String EXTRA_NAMESPACE = "namespace"; + + /** @hide - String argument extra to the config monitor callback */ + public static final String EXTRA_CALLING_PACKAGE = "calling_package"; + /** * Activity Extra: Limit available options in launched activity based on the given authority. * <p> @@ -14155,6 +14181,37 @@ public final class Settings { } } + /** + * Register callback for monitoring Config table. + * + * @param resolver Handle to the content resolver. + * @param callback callback to register + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.MONITOR_DEVICE_CONFIG_ACCESS) + public static void registerMonitorCallback(@NonNull ContentResolver resolver, + @NonNull RemoteCallback callback) { + registerMonitorCallbackAsUser(resolver, resolver.getUserId(), callback); + } + + private static void registerMonitorCallbackAsUser( + @NonNull ContentResolver resolver, @UserIdInt int userHandle, + @NonNull RemoteCallback callback) { + try { + Bundle arg = new Bundle(); + arg.putInt(CALL_METHOD_USER_KEY, userHandle); + arg.putParcelable(CALL_METHOD_MONITOR_CALLBACK_KEY, callback); + IContentProvider cp = sProviderHolder.getProvider(resolver); + cp.call(resolver.getPackageName(), resolver.getFeatureId(), + sProviderHolder.mUri.getAuthority(), + CALL_METHOD_REGISTER_MONITOR_CALLBACK_CONFIG, null, arg); + } catch (RemoteException e) { + Log.w(TAG, "Can't register config monitor callback", e); + } + } + private static String createCompositeName(@NonNull String namespace, @NonNull String name) { Preconditions.checkNotNull(namespace); Preconditions.checkNotNull(name); diff --git a/core/java/android/security/ConfirmationPrompt.java b/core/java/android/security/ConfirmationPrompt.java index 5330cffee3db..f67af85d00e3 100644 --- a/core/java/android/security/ConfirmationPrompt.java +++ b/core/java/android/security/ConfirmationPrompt.java @@ -212,20 +212,16 @@ public class ConfirmationPrompt { private int getUiOptionsAsFlags() { int uiOptionsAsFlags = 0; - try { - ContentResolver contentResolver = mContext.getContentResolver(); - int inversionEnabled = Settings.Secure.getInt(contentResolver, - Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED); - if (inversionEnabled == 1) { - uiOptionsAsFlags |= UI_OPTION_ACCESSIBILITY_INVERTED_FLAG; - } - float fontScale = Settings.System.getFloat(contentResolver, - Settings.System.FONT_SCALE); - if (fontScale > 1.0) { - uiOptionsAsFlags |= UI_OPTION_ACCESSIBILITY_MAGNIFIED_FLAG; - } - } catch (SettingNotFoundException e) { - Log.w(TAG, "Unexpected SettingNotFoundException"); + ContentResolver contentResolver = mContext.getContentResolver(); + int inversionEnabled = Settings.Secure.getInt(contentResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, 0); + if (inversionEnabled == 1) { + uiOptionsAsFlags |= UI_OPTION_ACCESSIBILITY_INVERTED_FLAG; + } + float fontScale = Settings.System.getFloat(contentResolver, + Settings.System.FONT_SCALE, (float) 1.0); + if (fontScale > 1.0) { + uiOptionsAsFlags |= UI_OPTION_ACCESSIBILITY_MAGNIFIED_FLAG; } return uiOptionsAsFlags; } diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java index c84fbc7287bb..8464c6df4425 100644 --- a/core/java/android/service/notification/Adjustment.java +++ b/core/java/android/service/notification/Adjustment.java @@ -131,6 +131,13 @@ public final class Adjustment implements Parcelable { public static final String KEY_RANKING_SCORE = "key_ranking_score"; /** + * Data type: boolean, when true it suggests this is NOT a conversation notification. + * @hide + */ + @SystemApi + public static final String KEY_NOT_CONVERSATION = "key_not_conversation"; + + /** * Create a notification adjustment. * * @param pkg The package of the notification. diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index fd04f499a432..e053ed58a82b 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -1539,6 +1539,7 @@ public abstract class NotificationListenerService extends Service { private ArrayList<CharSequence> mSmartReplies; private boolean mCanBubble; private boolean mVisuallyInterruptive; + private boolean mIsConversation; private static final int PARCEL_VERSION = 2; @@ -1571,6 +1572,7 @@ public abstract class NotificationListenerService extends Service { out.writeCharSequenceList(mSmartReplies); out.writeBoolean(mCanBubble); out.writeBoolean(mVisuallyInterruptive); + out.writeBoolean(mIsConversation); } /** @hide */ @@ -1604,6 +1606,7 @@ public abstract class NotificationListenerService extends Service { mSmartReplies = in.readCharSequenceList(); mCanBubble = in.readBoolean(); mVisuallyInterruptive = in.readBoolean(); + mIsConversation = in.readBoolean(); } @@ -1801,6 +1804,14 @@ public abstract class NotificationListenerService extends Service { } /** + * Returns whether this notification is a conversation notification. + * @hide + */ + public boolean isConversation() { + return mIsConversation; + } + + /** * @hide */ @VisibleForTesting @@ -1812,7 +1823,7 @@ public abstract class NotificationListenerService extends Service { int userSentiment, boolean hidden, long lastAudiblyAlertedMs, boolean noisy, ArrayList<Notification.Action> smartActions, ArrayList<CharSequence> smartReplies, boolean canBubble, - boolean visuallyInterruptive) { + boolean visuallyInterruptive, boolean isConversation) { mKey = key; mRank = rank; mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW; @@ -1834,6 +1845,7 @@ public abstract class NotificationListenerService extends Service { mSmartReplies = smartReplies; mCanBubble = canBubble; mVisuallyInterruptive = visuallyInterruptive; + mIsConversation = isConversation; } /** @@ -1859,7 +1871,8 @@ public abstract class NotificationListenerService extends Service { other.mSmartActions, other.mSmartReplies, other.mCanBubble, - other.mVisuallyInterruptive); + other.mVisuallyInterruptive, + other.mIsConversation); } /** @@ -1912,7 +1925,8 @@ public abstract class NotificationListenerService extends Service { == (other.mSmartActions == null ? 0 : other.mSmartActions.size())) && Objects.equals(mSmartReplies, other.mSmartReplies) && Objects.equals(mCanBubble, other.mCanBubble) - && Objects.equals(mVisuallyInterruptive, other.mVisuallyInterruptive); + && Objects.equals(mVisuallyInterruptive, other.mVisuallyInterruptive) + && Objects.equals(mIsConversation, other.mIsConversation); } } diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index 0f339988ba3e..1966f17aaf35 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -29,7 +29,6 @@ import android.hardware.soundtrigger.SoundTrigger; import android.hardware.soundtrigger.SoundTrigger.ConfidenceLevel; import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent; import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra; -import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; import android.media.AudioFormat; @@ -67,7 +66,12 @@ public class AlwaysOnHotwordDetector { /** * Indicates that recognition for the given keyphrase is not supported. * No further interaction should be performed with the detector that returns this availability. + * + * @deprecated This is no longer a valid state. Enrollment can occur outside of + * {@link KeyphraseEnrollmentInfo} through another privileged application. We can no longer + * determine ahead of time if the keyphrase and locale are unsupported by the system. */ + @Deprecated public static final int STATE_KEYPHRASE_UNSUPPORTED = -1; /** * Indicates that the given keyphrase is not enrolled. @@ -85,34 +89,6 @@ public class AlwaysOnHotwordDetector { */ private static final int STATE_NOT_READY = 0; - // Keyphrase management actions. Used in getManageIntent() ----// - @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = { "MANAGE_ACTION_" }, value = { - MANAGE_ACTION_ENROLL, - MANAGE_ACTION_RE_ENROLL, - MANAGE_ACTION_UN_ENROLL - }) - private @interface ManageActions {} - - /** - * Indicates that we need to enroll. - * - * @hide - */ - public static final int MANAGE_ACTION_ENROLL = 0; - /** - * Indicates that we need to re-enroll. - * - * @hide - */ - public static final int MANAGE_ACTION_RE_ENROLL = 1; - /** - * Indicates that we need to un-enroll. - * - * @hide - */ - public static final int MANAGE_ACTION_UN_ENROLL = 2; - //-- Flags for startRecognition ----// /** @hide */ @Retention(RetentionPolicy.SOURCE) @@ -248,7 +224,8 @@ public class AlwaysOnHotwordDetector { * The metadata of the Keyphrase, derived from the enrollment application. * This may be null if this keyphrase isn't supported by the enrollment application. */ - private final KeyphraseMetadata mKeyphraseMetadata; + @Nullable + private KeyphraseMetadata mKeyphraseMetadata; private final KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo; private final IVoiceInteractionService mVoiceInteractionService; private final IVoiceInteractionManagerService mModelManagementService; @@ -448,7 +425,6 @@ public class AlwaysOnHotwordDetector { mText = text; mLocale = locale; mKeyphraseEnrollmentInfo = keyphraseEnrollmentInfo; - mKeyphraseMetadata = mKeyphraseEnrollmentInfo.getKeyphraseMetadata(text, locale); mExternalCallback = callback; mHandler = new MyHandler(); mInternalCallback = new SoundTriggerListener(mHandler); @@ -484,8 +460,7 @@ public class AlwaysOnHotwordDetector { } // This method only makes sense if we can actually support a recognition. - if (mAvailability != STATE_KEYPHRASE_ENROLLED - && mAvailability != STATE_KEYPHRASE_UNENROLLED) { + if (mAvailability != STATE_KEYPHRASE_ENROLLED || mKeyphraseMetadata == null) { throw new UnsupportedOperationException( "Getting supported recognition modes for the keyphrase is not supported"); } @@ -679,7 +654,7 @@ public class AlwaysOnHotwordDetector { public Intent createEnrollIntent() { if (DBG) Slog.d(TAG, "createEnrollIntent"); synchronized (mLock) { - return getManageIntentLocked(MANAGE_ACTION_ENROLL); + return getManageIntentLocked(KeyphraseEnrollmentInfo.MANAGE_ACTION_ENROLL); } } @@ -700,7 +675,7 @@ public class AlwaysOnHotwordDetector { public Intent createUnEnrollIntent() { if (DBG) Slog.d(TAG, "createUnEnrollIntent"); synchronized (mLock) { - return getManageIntentLocked(MANAGE_ACTION_UN_ENROLL); + return getManageIntentLocked(KeyphraseEnrollmentInfo.MANAGE_ACTION_UN_ENROLL); } } @@ -721,11 +696,11 @@ public class AlwaysOnHotwordDetector { public Intent createReEnrollIntent() { if (DBG) Slog.d(TAG, "createReEnrollIntent"); synchronized (mLock) { - return getManageIntentLocked(MANAGE_ACTION_RE_ENROLL); + return getManageIntentLocked(KeyphraseEnrollmentInfo.MANAGE_ACTION_RE_ENROLL); } } - private Intent getManageIntentLocked(int action) { + private Intent getManageIntentLocked(@KeyphraseEnrollmentInfo.ManageActions int action) { if (mAvailability == STATE_INVALID) { throw new IllegalStateException("getManageIntent called on an invalid detector"); } @@ -761,8 +736,7 @@ public class AlwaysOnHotwordDetector { void onSoundModelsChanged() { synchronized (mLock) { if (mAvailability == STATE_INVALID - || mAvailability == STATE_HARDWARE_UNAVAILABLE - || mAvailability == STATE_KEYPHRASE_UNSUPPORTED) { + || mAvailability == STATE_HARDWARE_UNAVAILABLE) { Slog.w(TAG, "Received onSoundModelsChanged for an unsupported keyphrase/config"); return; } @@ -772,7 +746,9 @@ public class AlwaysOnHotwordDetector { // or was deleted. // The availability change callback should ensure that the client starts recognition // again if needed. - stopRecognitionLocked(); + if (mAvailability == STATE_KEYPHRASE_ENROLLED) { + stopRecognitionLocked(); + } // Execute a refresh availability task - which should then notify of a change. new RefreshAvailabiltyTask().execute(); @@ -955,20 +931,17 @@ public class AlwaysOnHotwordDetector { @Override public Void doInBackground(Void... params) { int availability = internalGetInitialAvailability(); - boolean enrolled = false; - // Fetch the sound model if the availability is one of the supported ones. - if (availability == STATE_NOT_READY - || availability == STATE_KEYPHRASE_UNENROLLED - || availability == STATE_KEYPHRASE_ENROLLED) { - enrolled = internalGetIsEnrolled(mKeyphraseMetadata.id, mLocale); - if (!enrolled) { - availability = STATE_KEYPHRASE_UNENROLLED; - } else { - availability = STATE_KEYPHRASE_ENROLLED; - } - } synchronized (mLock) { + if (availability == STATE_NOT_READY) { + internalUpdateEnrolledKeyphraseMetadata(); + if (mKeyphraseMetadata != null) { + availability = STATE_KEYPHRASE_ENROLLED; + } else { + availability = STATE_KEYPHRASE_UNENROLLED; + } + } + if (DBG) { Slog.d(TAG, "Hotword availability changed from " + mAvailability + " -> " + availability); @@ -997,28 +970,22 @@ public class AlwaysOnHotwordDetector { } catch (RemoteException e) { Slog.w(TAG, "RemoteException in getDspProperties!", e); } + // No DSP available if (dspModuleProperties == null) { return STATE_HARDWARE_UNAVAILABLE; } - // No enrollment application supports this keyphrase/locale - if (mKeyphraseMetadata == null) { - return STATE_KEYPHRASE_UNSUPPORTED; - } + return STATE_NOT_READY; } - /** - * @return The corresponding {@link KeyphraseSoundModel} or null if none is found. - */ - private boolean internalGetIsEnrolled(int keyphraseId, Locale locale) { + private void internalUpdateEnrolledKeyphraseMetadata() { try { - return mModelManagementService.isEnrolledForKeyphrase( - mVoiceInteractionService, keyphraseId, locale.toLanguageTag()); + mKeyphraseMetadata = mModelManagementService.getEnrolledKeyphraseMetadata( + mVoiceInteractionService, mText, mLocale.toLanguageTag()); } catch (RemoteException e) { - Slog.w(TAG, "RemoteException in listRegisteredKeyphraseSoundModels!", e); + Slog.w(TAG, "RemoteException in internalUpdateEnrolledKeyphraseMetadata", e); } - return false; } } diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index 36e057f4a97d..fc99836b82fd 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -16,14 +16,18 @@ package android.service.voice; +import android.Manifest; import android.annotation.NonNull; +import android.annotation.RequiresPermission; import android.annotation.SdkConstant; +import android.annotation.SystemApi; import android.app.Service; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.hardware.soundtrigger.KeyphraseEnrollmentInfo; +import android.media.voice.KeyphraseModelManager; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -304,6 +308,23 @@ public class VoiceInteractionService extends Service { } /** + * Creates an {@link KeyphraseModelManager} to use for enrolling voice models outside of the + * pre-bundled system voice models. + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES) + @NonNull + public final KeyphraseModelManager createKeyphraseModelManager() { + if (mSystemService == null) { + throw new IllegalStateException("Not available until onReady() is called"); + } + synchronized (mLock) { + return new KeyphraseModelManager(mSystemService); + } + } + + /** * @return Details of keyphrases available for enrollment. * @hide */ diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java index 52b72949ee04..5cb52ea0685e 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -1137,7 +1137,7 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall mWindow = new SoftInputWindow(mContext, "VoiceInteractionSession", mTheme, mCallbacks, this, mDispatcherState, WindowManager.LayoutParams.TYPE_VOICE_INTERACTION, Gravity.BOTTOM, true); - mWindow.getWindow().setFitWindowInsetsTypes(0 /* types */); + mWindow.getWindow().getAttributes().setFitWindowInsetsTypes(0 /* types */); mWindow.getWindow().addFlags( WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl index 00e0b7c170be..84b6869bf620 100644 --- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl +++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl @@ -37,4 +37,5 @@ oneway interface IWallpaperEngine { void requestWallpaperColors(); @UnsupportedAppUsage void destroy(); + void setZoomOut(float scale); } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index e3fd8d297316..91691436a87a 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -16,6 +16,7 @@ package android.service.wallpaper; +import android.annotation.FloatRange; import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; @@ -122,6 +123,9 @@ public abstract class WallpaperService extends Service { private static final int MSG_WINDOW_MOVED = 10035; private static final int MSG_TOUCH_EVENT = 10040; private static final int MSG_REQUEST_WALLPAPER_COLORS = 10050; + private static final int MSG_SCALE = 10100; + + private static final float MAX_SCALE = 1.15f; private static final int NOTIFY_COLORS_RATE_LIMIT_MS = 1000; @@ -170,6 +174,7 @@ public abstract class WallpaperService extends Service { int mType; int mCurWidth; int mCurHeight; + float mZoom = 0f; int mWindowFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; int mWindowPrivateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS; @@ -496,6 +501,15 @@ public abstract class WallpaperService extends Service { } /** + * Returns the current scale of the surface + * @hide + */ + @VisibleForTesting + public float getZoom() { + return mZoom; + } + + /** * Called once to initialize the engine. After returning, the * engine's surface will be created by the framework. */ @@ -623,6 +637,16 @@ public abstract class WallpaperService extends Service { } /** + * Called when the zoom level of the wallpaper changed. + * This method will be called with the initial zoom level when the surface is created. + * + * @param zoom the zoom level, between 0 indicating fully zoomed in and 1 indicating fully + * zoomed out. + */ + public void onZoomChanged(@FloatRange(from = 0f, to = 1f) float zoom) { + } + + /** * Notifies the engine that wallpaper colors changed significantly. * This will trigger a {@link #onComputeColors()} call. */ @@ -706,6 +730,7 @@ public abstract class WallpaperService extends Service { out.print(prefix); out.print("mConfiguration="); out.println(mMergedConfiguration.getMergedConfiguration()); out.print(prefix); out.print("mLayout="); out.println(mLayout); + out.print(prefix); out.print("mZoom="); out.println(mZoom); synchronized (mLock) { out.print(prefix); out.print("mPendingXOffset="); out.print(mPendingXOffset); out.print(" mPendingXOffset="); out.println(mPendingXOffset); @@ -721,6 +746,37 @@ public abstract class WallpaperService extends Service { } } + /** + * Set the wallpaper zoom to the given value. This value will be ignored when in ambient + * mode (and zoom will be reset to 0). + * @hide + * @param zoom between 0 and 1 (inclusive) indicating fully zoomed in to fully zoomed out + * respectively. + */ + @VisibleForTesting + public void setZoom(float zoom) { + if (DEBUG) { + Log.v(TAG, "set zoom received: " + zoom); + } + boolean updated = false; + synchronized (mLock) { + if (DEBUG) { + Log.v(TAG, "mZoom: " + mZoom + " updated: " + zoom); + } + if (mIsInAmbientMode) { + mZoom = 0; + } + if (Float.compare(zoom, mZoom) != 0) { + mZoom = zoom; + updated = true; + } + } + if (DEBUG) Log.v(TAG, "setZoom updated? " + updated); + if (updated && !mDestroyed) { + onZoomChanged(mZoom); + } + } + private void dispatchPointer(MotionEvent event) { if (event.isTouchEvent()) { synchronized (mLock) { @@ -920,6 +976,7 @@ public abstract class WallpaperService extends Service { c.surfaceCreated(mSurfaceHolder); } } + onZoomChanged(0f); } redrawNeeded |= creating || (relayoutResult @@ -1080,6 +1137,7 @@ public abstract class WallpaperService extends Service { mIsInAmbientMode = inAmbientMode; if (mCreated) { onAmbientModeChanged(inAmbientMode, animationDuration); + setZoom(0); } } } @@ -1354,6 +1412,11 @@ public abstract class WallpaperService extends Service { } } + public void setZoomOut(float scale) { + Message msg = mCaller.obtainMessageI(MSG_SCALE, Float.floatToIntBits(scale)); + mCaller.sendMessage(msg); + } + public void reportShown() { if (!mShownReported) { mShownReported = true; @@ -1426,6 +1489,9 @@ public abstract class WallpaperService extends Service { case MSG_UPDATE_SURFACE: mEngine.updateSurface(true, false, false); break; + case MSG_SCALE: + mEngine.setZoom(Float.intBitsToFloat(message.arg1)); + break; case MSG_VISIBILITY_CHANGED: if (DEBUG) Log.v(TAG, "Visibility change in " + mEngine + ": " + message.arg1); diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java index e25826c25e35..57375919e6cd 100644 --- a/core/java/android/telephony/TelephonyRegistryManager.java +++ b/core/java/android/telephony/TelephonyRegistryManager.java @@ -28,8 +28,10 @@ import android.telephony.Annotation.ApnType; import android.telephony.Annotation.CallState; import android.telephony.Annotation.DataActivityType; import android.telephony.Annotation.DataFailureCause; +import android.telephony.Annotation.DisconnectCauses; import android.telephony.Annotation.NetworkType; import android.telephony.Annotation.PreciseCallStates; +import android.telephony.Annotation.PreciseDisconnectCauses; import android.telephony.Annotation.RadioPowerState; import android.telephony.Annotation.SimActivationState; import android.telephony.Annotation.SrvccState; @@ -56,6 +58,7 @@ import java.util.concurrent.Executor; * @hide */ @SystemApi +@TestApi public class TelephonyRegistryManager { private static final String TAG = "TelephonyRegistryManager"; @@ -225,11 +228,9 @@ public class TelephonyRegistryManager { * invalid. * @param state latest call state. e.g, offhook, ringing * @param incomingNumber incoming phone number. - * - * @hide */ public void notifyCallStateChanged(int subId, int slotIndex, @CallState int state, - String incomingNumber) { + @Nullable String incomingNumber) { try { sRegistry.notifyCallState(slotIndex, subId, state, incomingNumber); } catch (RemoteException ex) { @@ -263,10 +264,8 @@ public class TelephonyRegistryManager { * @param slotIndex for which the service state changed. Can be derived from subId except * subId is invalid. * @param state service state e.g, in service, out of service or roaming status. - * - * @hide */ - public void notifyServiceStateChanged(int subId, int slotIndex, ServiceState state) { + public void notifyServiceStateChanged(int subId, int slotIndex, @NonNull ServiceState state) { try { sRegistry.notifyServiceStateForPhoneId(slotIndex, subId, state); } catch (RemoteException ex) { @@ -281,11 +280,9 @@ public class TelephonyRegistryManager { * @param slotIndex for which the signalstrength changed. Can be derived from subId except when * subId is invalid. * @param signalStrength e.g, signalstrength level {@see SignalStrength#getLevel()} - * - * @hide */ public void notifySignalStrengthChanged(int subId, int slotIndex, - SignalStrength signalStrength) { + @NonNull SignalStrength signalStrength) { try { sRegistry.notifySignalStrengthForPhoneId(slotIndex, subId, signalStrength); } catch (RemoteException ex) { @@ -302,8 +299,6 @@ public class TelephonyRegistryManager { * except when subId is invalid. * @param msgWaitingInd {@code true} indicates there is message-waiting indicator, {@code false} * otherwise. - * - * @hide */ public void notifyMessageWaitingChanged(int subId, int slotIndex, boolean msgWaitingInd) { try { @@ -319,8 +314,6 @@ public class TelephonyRegistryManager { * @param subId for which call forwarding status changed. * @param callForwardInd {@code true} indicates there is call forwarding, {@code false} * otherwise. - * - * @hide */ public void notifyCallForwardingChanged(int subId, boolean callForwardInd) { try { @@ -336,8 +329,6 @@ public class TelephonyRegistryManager { * @param subId for which data activity state changed. * @param dataActivityType indicates the latest data activity type e.g, {@link * TelephonyManager#DATA_ACTIVITY_IN} - * - * @hide */ public void notifyDataActivityChanged(int subId, @DataActivityType int dataActivityType) { try { @@ -358,10 +349,9 @@ public class TelephonyRegistryManager { * * @see android.telephony.PreciseDataConnection * @see TelephonyManager#DATA_DISCONNECTED - * @hide */ public void notifyDataConnectionForSubscriber(int slotIndex, int subId, - @ApnType int apnType, PreciseDataConnectionState preciseState) { + @ApnType int apnType, @Nullable PreciseDataConnectionState preciseState) { try { sRegistry.notifyDataConnectionForSubscriber( slotIndex, subId, apnType, preciseState); @@ -378,10 +368,8 @@ public class TelephonyRegistryManager { * subId is invalid. * @param callQuality Information about call quality e.g, call quality level * @param networkType associated with this data connection. e.g, LTE - * - * @hide */ - public void notifyCallQualityChanged(int subId, int slotIndex, CallQuality callQuality, + public void notifyCallQualityChanged(int subId, int slotIndex, @NonNull CallQuality callQuality, @NetworkType int networkType) { try { sRegistry.notifyCallQualityChanged(callQuality, slotIndex, subId, networkType); @@ -396,8 +384,6 @@ public class TelephonyRegistryManager { * @param subId for which emergency number list changed. * @param slotIndex for which emergency number list changed. Can be derived from subId except * when subId is invalid. - * - * @hide */ public void notifyEmergencyNumberList(int subId, int slotIndex) { try { @@ -414,8 +400,6 @@ public class TelephonyRegistryManager { * @param slotIndex for which radio power state changed. Can be derived from subId except when * subId is invalid. * @param radioPowerState the current modem radio state. - * - * @hide */ public void notifyRadioPowerStateChanged(int subId, int slotIndex, @RadioPowerState int radioPowerState) { @@ -430,10 +414,8 @@ public class TelephonyRegistryManager { * Notify {@link PhoneCapability} changed. * * @param phoneCapability the capability of the modem group. - * - * @hide */ - public void notifyPhoneCapabilityChanged(PhoneCapability phoneCapability) { + public void notifyPhoneCapabilityChanged(@NonNull PhoneCapability phoneCapability) { try { sRegistry.notifyPhoneCapabilityChanged(phoneCapability); } catch (RemoteException ex) { @@ -462,8 +444,6 @@ public class TelephonyRegistryManager { * @param slotIndex for which data activation state changed. Can be derived from subId except * when subId is invalid. * @param activationState sim activation state e.g, activated. - * - * @hide */ public void notifyDataActivationStateChanged(int subId, int slotIndex, @SimActivationState int activationState) { @@ -483,8 +463,6 @@ public class TelephonyRegistryManager { * @param slotIndex for which voice activation state changed. Can be derived from subId except * subId is invalid. * @param activationState sim activation state e.g, activated. - * - * @hide */ public void notifyVoiceActivationStateChanged(int subId, int slotIndex, @SimActivationState int activationState) { @@ -504,8 +482,6 @@ public class TelephonyRegistryManager { * @param slotIndex for which mobile data state has changed. Can be derived from subId except * when subId is invalid. * @param state {@code true} indicates mobile data is enabled/on. {@code false} otherwise. - * - * @hide */ public void notifyUserMobileDataStateChanged(int slotIndex, int subId, boolean state) { try { @@ -516,31 +492,12 @@ public class TelephonyRegistryManager { } /** - * TODO: this is marked as deprecated, can we move this one safely? - * - * @param subId - * @param slotIndex - * @param rawData - * - * @hide - */ - public void notifyOemHookRawEventForSubscriber(int subId, int slotIndex, byte[] rawData) { - try { - sRegistry.notifyOemHookRawEventForSubscriber(slotIndex, subId, rawData); - } catch (RemoteException ex) { - // system process is dead - } - } - - /** * Notify IMS call disconnect causes which contains {@link android.telephony.ims.ImsReasonInfo}. * * @param subId for which ims call disconnect. * @param imsReasonInfo the reason for ims call disconnect. - * - * @hide */ - public void notifyImsDisconnectCause(int subId, ImsReasonInfo imsReasonInfo) { + public void notifyImsDisconnectCause(int subId, @NonNull ImsReasonInfo imsReasonInfo) { try { sRegistry.notifyImsDisconnectCause(subId, imsReasonInfo); } catch (RemoteException ex) { @@ -557,11 +514,9 @@ public class TelephonyRegistryManager { * @param apnType the apn type bitmask, defined with {@code ApnSetting#TYPE_*} flags. * @param apn the APN {@link ApnSetting#getApnName()} of this data connection. * @param failCause data fail cause. - * - * @hide */ public void notifyPreciseDataConnectionFailed(int subId, int slotIndex, @ApnType int apnType, - String apn, @DataFailureCause int failCause) { + @Nullable String apn, @DataFailureCause int failCause) { try { sRegistry.notifyPreciseDataConnectionFailed(slotIndex, subId, apnType, apn, failCause); } catch (RemoteException ex) { @@ -575,8 +530,6 @@ public class TelephonyRegistryManager { * * @param subId for which srvcc state changed. * @param state srvcc state - * - * @hide */ public void notifySrvccStateChanged(int subId, @SrvccState int state) { try { @@ -596,8 +549,6 @@ public class TelephonyRegistryManager { * @param ringCallPreciseState ringCall state. * @param foregroundCallPreciseState foreground call state. * @param backgroundCallPreciseState background call state. - * - * @hide */ public void notifyPreciseCallState(int subId, int slotIndex, @PreciseCallStates int ringCallPreciseState, @@ -621,10 +572,9 @@ public class TelephonyRegistryManager { * @param cause {@link DisconnectCause} for the disconnected call. * @param preciseCause {@link android.telephony.PreciseDisconnectCause} for the disconnected * call. - * - * @hide */ - public void notifyDisconnectCause(int slotIndex, int subId, int cause, int preciseCause) { + public void notifyDisconnectCause(int slotIndex, int subId, @DisconnectCauses int cause, + @PreciseDisconnectCauses int preciseCause) { try { sRegistry.notifyDisconnectCause(slotIndex, subId, cause, preciseCause); } catch (RemoteException ex) { @@ -637,10 +587,8 @@ public class TelephonyRegistryManager { * * <p>To be compatible with {@link TelephonyRegistry}, use {@link CellIdentity} which is * parcelable, and convert to CellLocation in client code. - * - * @hide */ - public void notifyCellLocation(int subId, CellIdentity cellLocation) { + public void notifyCellLocation(int subId, @NonNull CellIdentity cellLocation) { try { sRegistry.notifyCellLocationForSubscriber(subId, cellLocation); } catch (RemoteException ex) { @@ -654,10 +602,8 @@ public class TelephonyRegistryManager { * * @param subId for which cellinfo changed. * @param cellInfo A list of cellInfo associated with the given subscription. - * - * @hide */ - public void notifyCellInfoChanged(int subId, List<CellInfo> cellInfo) { + public void notifyCellInfoChanged(int subId, @NonNull List<CellInfo> cellInfo) { try { sRegistry.notifyCellInfoForSubscriber(subId, cellInfo); } catch (RemoteException ex) { @@ -666,8 +612,8 @@ public class TelephonyRegistryManager { } /** - * @param activeDataSubId - * @hide + * Notify that the active data subscription ID has changed. + * @param activeDataSubId The new subscription ID for active data */ public void notifyActiveDataSubIdChanged(int activeDataSubId) { try { diff --git a/core/java/android/timezone/CountryTimeZones.java b/core/java/android/timezone/CountryTimeZones.java index ada59d6d7d55..e5bbdf4b6eea 100644 --- a/core/java/android/timezone/CountryTimeZones.java +++ b/core/java/android/timezone/CountryTimeZones.java @@ -43,6 +43,7 @@ public final class CountryTimeZones { @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final class TimeZoneMapping { + @NonNull private libcore.timezone.CountryTimeZones.TimeZoneMapping mDelegate; TimeZoneMapping(libcore.timezone.CountryTimeZones.TimeZoneMapping delegate) { diff --git a/core/java/android/timezone/TelephonyLookup.java b/core/java/android/timezone/TelephonyLookup.java index 39dbe85cb485..eebccf4aa577 100644 --- a/core/java/android/timezone/TelephonyLookup.java +++ b/core/java/android/timezone/TelephonyLookup.java @@ -36,12 +36,8 @@ public class TelephonyLookup { @GuardedBy("sLock") private static TelephonyLookup sInstance; - @NonNull - private final libcore.timezone.TelephonyLookup mDelegate; - /** - * Obtains an instance for use when resolving telephony time zone information. This method never - * returns {@code null}. + * Obtains an instance for use when resolving telephony time zone information. */ @NonNull public static TelephonyLookup getInstance() { @@ -53,6 +49,9 @@ public class TelephonyLookup { } } + @NonNull + private final libcore.timezone.TelephonyLookup mDelegate; + private TelephonyLookup(@NonNull libcore.timezone.TelephonyLookup delegate) { mDelegate = Objects.requireNonNull(delegate); } diff --git a/core/java/android/timezone/TelephonyNetworkFinder.java b/core/java/android/timezone/TelephonyNetworkFinder.java index a81a516c4b33..079d0882f191 100644 --- a/core/java/android/timezone/TelephonyNetworkFinder.java +++ b/core/java/android/timezone/TelephonyNetworkFinder.java @@ -23,7 +23,7 @@ import android.annotation.SystemApi; import java.util.Objects; /** - * A class that can find telephony networks loaded via {@link TelephonyLookup}. + * A class that can find telephony network information loaded via {@link TelephonyLookup}. * * @hide */ diff --git a/core/java/android/timezone/TimeZoneFinder.java b/core/java/android/timezone/TimeZoneFinder.java index 15dfe62bb789..9327b001a9c8 100644 --- a/core/java/android/timezone/TimeZoneFinder.java +++ b/core/java/android/timezone/TimeZoneFinder.java @@ -22,8 +22,10 @@ import android.annotation.SystemApi; import com.android.internal.annotations.GuardedBy; +import java.util.Objects; + /** - * A class that can be used to find time zones. + * A class that can be used to find time zones using information like country and offset. * * @hide */ @@ -34,15 +36,8 @@ public final class TimeZoneFinder { @GuardedBy("sLock") private static TimeZoneFinder sInstance; - private final libcore.timezone.TimeZoneFinder mDelegate; - - private TimeZoneFinder(libcore.timezone.TimeZoneFinder delegate) { - mDelegate = delegate; - } - /** - * Obtains an instance for use when resolving telephony time zone information. This method never - * returns {@code null}. + * Obtains the singleton instance. */ @NonNull public static TimeZoneFinder getInstance() { @@ -54,6 +49,22 @@ public final class TimeZoneFinder { return sInstance; } + @NonNull + private final libcore.timezone.TimeZoneFinder mDelegate; + + private TimeZoneFinder(@NonNull libcore.timezone.TimeZoneFinder delegate) { + mDelegate = Objects.requireNonNull(delegate); + } + + /** + * Returns the IANA rules version associated with the data. If there is no version information + * or there is a problem reading the file then {@code null} is returned. + */ + @Nullable + public String getIanaVersion() { + return mDelegate.getIanaVersion(); + } + /** * Returns a {@link CountryTimeZones} object associated with the specified country code. * Caching is handled as needed. If the country code is not recognized or there is an error diff --git a/core/java/android/timezone/TzDataSetVersion.java b/core/java/android/timezone/TzDataSetVersion.java new file mode 100644 index 000000000000..aba7c4c15903 --- /dev/null +++ b/core/java/android/timezone/TzDataSetVersion.java @@ -0,0 +1,154 @@ +/* + * 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.timezone; + +import android.annotation.NonNull; +import android.annotation.SystemApi; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.IOException; +import java.util.Objects; + +/** + * Version information associated with the set of time zone data on a device. + * + * <p>Time Zone Data Sets have a major ({@link #getFormatMajorVersion()}) and minor + * ({@link #currentFormatMinorVersion()}) version number: + * <ul> + * <li>Major version numbers are mutually incompatible. e.g. v2 is not compatible with a v1 or a + * v3 device.</li> + * <li>Minor version numbers are backwards compatible. e.g. a v2.2 data set will work + * on a v2.1 device but not a v2.3 device. The minor version is reset to 1 when the major version + * is incremented.</li> + * </ul> + * + * <p>Data sets contain time zone rules and other data associated wtih a tzdb release + * ({@link #getRulesVersion()}) and an additional Android-specific revision number + * ({@link #getRevision()}). + * + * <p>See platform/system/timezone/README.android for more information. + * @hide + */ +@VisibleForTesting +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public final class TzDataSetVersion { + + /** + * Returns the major tz data format version supported by this device. + */ + public static int currentFormatMajorVersion() { + return libcore.timezone.TzDataSetVersion.currentFormatMajorVersion(); + } + + /** + * Returns the minor tz data format version supported by this device. + */ + public static int currentFormatMinorVersion() { + return libcore.timezone.TzDataSetVersion.currentFormatMinorVersion(); + } + + /** + * Returns true if the version information provided would be compatible with this device, i.e. + * with the current system image, and set of active modules. + */ + public static boolean isCompatibleWithThisDevice(TzDataSetVersion tzDataSetVersion) { + return libcore.timezone.TzDataSetVersion.isCompatibleWithThisDevice( + tzDataSetVersion.mDelegate); + } + + /** + * Reads the current Android time zone data set version file. + */ + @NonNull + public static TzDataSetVersion read() throws IOException, TzDataSetException { + try { + return new TzDataSetVersion( + libcore.timezone.TzDataSetVersion.readTimeZoneModuleVersion()); + } catch (libcore.timezone.TzDataSetVersion.TzDataSetException e) { + throw new TzDataSetException(e.getMessage(), e); + } + } + + /** + * A checked exception used in connection with time zone data sets. + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static class TzDataSetException extends Exception { + + /** Creates an instance with a message. */ + public TzDataSetException(String message) { + super(message); + } + + /** Creates an instance with a message and a cause. */ + public TzDataSetException(String message, Throwable cause) { + super(message, cause); + } + } + + @NonNull + private final libcore.timezone.TzDataSetVersion mDelegate; + + private TzDataSetVersion(@NonNull libcore.timezone.TzDataSetVersion delegate) { + mDelegate = Objects.requireNonNull(delegate); + } + + /** Returns the major version number. See {@link TzDataSetVersion}. */ + public int getFormatMajorVersion() { + return mDelegate.formatMajorVersion; + } + + /** Returns the minor version number. See {@link TzDataSetVersion}. */ + public int getFormatMinorVersion() { + return mDelegate.formatMinorVersion; + } + + /** Returns the tzdb version string. See {@link TzDataSetVersion}. */ + @NonNull + public String getRulesVersion() { + return mDelegate.rulesVersion; + } + + /** Returns the Android revision. See {@link TzDataSetVersion}. */ + public int getRevision() { + return mDelegate.revision; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TzDataSetVersion that = (TzDataSetVersion) o; + return mDelegate.equals(that.mDelegate); + } + + @Override + public int hashCode() { + return Objects.hash(mDelegate); + } + + @Override + public String toString() { + return mDelegate.toString(); + } +} diff --git a/core/java/android/timezone/ZoneInfoDb.java b/core/java/android/timezone/ZoneInfoDb.java new file mode 100644 index 000000000000..eb191e8e3272 --- /dev/null +++ b/core/java/android/timezone/ZoneInfoDb.java @@ -0,0 +1,66 @@ +/* + * 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.timezone; + +import android.annotation.NonNull; +import android.annotation.SystemApi; + +import com.android.internal.annotations.GuardedBy; + +import java.util.Objects; + +/** + * Android's internal factory for java.util.TimeZone objects. Provides access to core library time + * zone metadata not available via {@link java.util.TimeZone}. + * + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public final class ZoneInfoDb { + + private static Object sLock = new Object(); + @GuardedBy("sLock") + private static ZoneInfoDb sInstance; + + /** + * Obtains the singleton instance. + */ + @NonNull + public static ZoneInfoDb getInstance() { + synchronized (sLock) { + if (sInstance == null) { + sInstance = new ZoneInfoDb(libcore.timezone.ZoneInfoDB.getInstance()); + } + } + return sInstance; + } + + @NonNull + private final libcore.timezone.ZoneInfoDB mDelegate; + + private ZoneInfoDb(libcore.timezone.ZoneInfoDB delegate) { + mDelegate = Objects.requireNonNull(delegate); + } + + /** + * Returns the tzdb version in use. + */ + @NonNull + public String getVersion() { + return mDelegate.getVersion(); + } +} diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index eb4af1c2a979..06fccaf8ea81 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -44,6 +44,9 @@ public class FeatureFlagUtils { public static final String SETTINGS_FUSE_FLAG = "settings_fuse"; public static final String NOTIF_CONVO_BYPASS_SHORTCUT_REQ = "settings_notif_convo_bypass_shortcut_req"; + /** @hide */ + public static final String BACKUP_NO_KV_DATA_CHANGE_CALLS = + "backup_enable_no_data_notification_calls"; private static final Map<String, String> DEFAULT_FLAGS; @@ -62,6 +65,9 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put("settings_controller_loading_enhancement", "false"); DEFAULT_FLAGS.put("settings_conditionals", "false"); DEFAULT_FLAGS.put(NOTIF_CONVO_BYPASS_SHORTCUT_REQ, "true"); + + // Disabled until backup transports support it. + DEFAULT_FLAGS.put(BACKUP_NO_KV_DATA_CHANGE_CALLS, "false"); } /** diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java index 0558204af4c9..37dd781d4468 100644 --- a/core/java/android/util/TimeUtils.java +++ b/core/java/android/util/TimeUtils.java @@ -89,7 +89,7 @@ public class TimeUtils { * * <p>The list returned may be different from other on-device sources like * {@link android.icu.util.TimeZone#getRegion(String)} as it can be curated to avoid - * contentious mappings. + * contentious or obsolete mappings. * * @param countryCode the ISO 3166-1 alpha-2 code for the country as can be obtained using * {@link java.util.Locale#getCountry()} diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 904c510a5b01..0304328f734a 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -423,10 +423,14 @@ public final class Display { /** * Internal method to create a display. * The display created with this method will have a static {@link DisplayAdjustments} applied. - * Applications should use {@link android.view.WindowManager#getDefaultDisplay()} - * or {@link android.hardware.display.DisplayManager#getDisplay} - * to get a display object. + * Applications should use {@link android.content.Context#getDisplay} with + * {@link android.app.Activity} or a context associated with a {@link Display} via + * {@link android.content.Context#createDisplayContext(Display)} + * to get a display object associated with a {@link android.app.Context}, or + * {@link android.hardware.display.DisplayManager#getDisplay} to get a display object by id. * + * @see android.content.Context#getDisplay() + * @see android.content.Context#createDisplayContext(Display) * @hide */ public Display(DisplayManagerGlobal global, int displayId, /*@NotNull*/ DisplayInfo displayInfo, diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 993bdc4d6543..5eb319f19cce 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -111,6 +111,20 @@ interface IWindowManager // These can only be called when holding the MANAGE_APP_TOKENS permission. void setEventDispatching(boolean enabled); + + /** @return {@code true} if this binder is a registered window token. */ + boolean isWindowToken(in IBinder binder); + /** + * Adds window token for a given type. + * + * @param token Token to be registered. + * @param type Window type to be used with this token. + * @param displayId The ID of the display where this token should be added. + * @param packageName The name of package to request to add window token. + * @return {@link WindowManagerGlobal#ADD_OKAY} if the addition was successful, an error code + * otherwise. + */ + int addWindowContextToken(IBinder token, int type, int displayId, String packageName); void addWindowToken(IBinder token, int type, int displayId); void removeWindowToken(IBinder token, int displayId); void prepareAppTransition(int transit, boolean alwaysKeepCurrent); diff --git a/core/java/android/view/ImeFocusController.java b/core/java/android/view/ImeFocusController.java index 271566acb74e..8d58ee83cd67 100644 --- a/core/java/android/view/ImeFocusController.java +++ b/core/java/android/view/ImeFocusController.java @@ -45,12 +45,25 @@ public final class ImeFocusController { mViewRootImpl = viewRootImpl; } + @NonNull private InputMethodManagerDelegate getImmDelegate() { - if (mDelegate == null) { - mDelegate = mViewRootImpl.mContext.getSystemService( - InputMethodManager.class).getDelegate(); + InputMethodManagerDelegate delegate = mDelegate; + if (delegate != null) { + return delegate; } - return mDelegate; + delegate = mViewRootImpl.mContext.getSystemService(InputMethodManager.class).getDelegate(); + mDelegate = delegate; + return delegate; + } + + /** Called when the view root is moved to a different display. */ + @UiThread + void onMovedToDisplay() { + // InputMethodManager managed its instances for different displays. So if the associated + // display is changed, the delegate also needs to be refreshed (by getImmDelegate). + // See the comment in {@link android.app.SystemServiceRegistry} for InputMethodManager + // and {@link android.view.inputmethod.InputMethodManager#forContext}. + mDelegate = null; } @UiThread @@ -103,7 +116,8 @@ public final class ImeFocusController { } boolean forceFocus = false; - if (getImmDelegate().isRestartOnNextWindowFocus(true /* reset */)) { + final InputMethodManagerDelegate immDelegate = getImmDelegate(); + if (immDelegate.isRestartOnNextWindowFocus(true /* reset */)) { if (DEBUG) Log.v(TAG, "Restarting due to isRestartOnNextWindowFocus as true"); forceFocus = true; } @@ -111,12 +125,13 @@ public final class ImeFocusController { final View viewForWindowFocus = focusedView != null ? focusedView : mViewRootImpl.mView; onViewFocusChanged(viewForWindowFocus, true); - getImmDelegate().startInputAsyncOnWindowFocusGain(viewForWindowFocus, + immDelegate.startInputAsyncOnWindowFocusGain(viewForWindowFocus, windowAttribute.softInputMode, windowAttribute.flags, forceFocus); } public boolean checkFocus(boolean forceNewFocus, boolean startInput) { - if (!getImmDelegate().isCurrentRootView(mViewRootImpl) + final InputMethodManagerDelegate immDelegate = getImmDelegate(); + if (!immDelegate.isCurrentRootView(mViewRootImpl) || (mServedView == mNextServedView && !forceNewFocus)) { return false; } @@ -128,15 +143,16 @@ public final class ImeFocusController { // Close the connection when no next served view coming. if (mNextServedView == null) { - getImmDelegate().finishInput(); - getImmDelegate().closeCurrentIme(); + immDelegate.finishInput(); + immDelegate.closeCurrentIme(); return false; } mServedView = mNextServedView; - getImmDelegate().finishComposingText(); + immDelegate.finishComposingText(); if (startInput) { - getImmDelegate().startInput(StartInputReason.CHECK_FOCUS, null, 0, 0, 0); + immDelegate.startInput(StartInputReason.CHECK_FOCUS, null /* focusedView */, + 0 /* startInputFlags */, 0 /* softInputMode */, 0 /* windowFlags */); } return true; } @@ -169,13 +185,14 @@ public final class ImeFocusController { @UiThread void onWindowDismissed() { - if (!getImmDelegate().isCurrentRootView(mViewRootImpl)) { + final InputMethodManagerDelegate immDelegate = getImmDelegate(); + if (!immDelegate.isCurrentRootView(mViewRootImpl)) { return; } if (mServedView != null) { - getImmDelegate().finishInput(); + immDelegate.finishInput(); } - getImmDelegate().setCurrentRootView(null); + immDelegate.setCurrentRootView(null); mHasImeFocus = false; } diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index f5afd106a4a7..405eccd56e3c 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -40,6 +40,7 @@ import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsAnimationCallback.AnimationBounds; import android.view.WindowInsetsAnimationCallback.InsetsAnimation; import android.view.WindowManager.LayoutParams; +import android.view.animation.Interpolator; import com.android.internal.annotations.VisibleForTesting; @@ -84,8 +85,8 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll public InsetsAnimationControlImpl(SparseArray<InsetsSourceControl> controls, Rect frame, InsetsState state, WindowInsetsAnimationControlListener listener, @InsetsType int types, - InsetsAnimationControlCallbacks controller, long durationMs, boolean fade, - @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) { + InsetsAnimationControlCallbacks controller, long durationMs, Interpolator interpolator, + boolean fade, @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) { mControls = controls; mListener = listener; mTypes = types; @@ -101,8 +102,8 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll mFrame = new Rect(frame); buildTypeSourcesMap(mTypeSideMap, mSideSourceMap, mControls); - mAnimation = new WindowInsetsAnimationCallback.InsetsAnimation(mTypes, - InsetsController.INTERPOLATOR, durationMs); + mAnimation = new WindowInsetsAnimationCallback.InsetsAnimation(mTypes, interpolator, + durationMs); mAnimation.setAlpha(getCurrentAlpha()); mController.startAnimation(this, listener, types, mAnimation, new AnimationBounds(mHiddenInsets, mShownInsets), layoutInsetsDuringAnimation); @@ -196,7 +197,7 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll state.getSource(control.getType()).setVisible(shown); } Insets insets = getInsetsFromState(state, mFrame, null /* typeSideMap */); - setInsetsAndAlpha(insets, 1f /* alpha */, shown ? 1f : 0f /* fraction */); + setInsetsAndAlpha(insets, 1f /* alpha */, 1f /* fraction */); mFinished = true; mShownOnFinish = shown; } diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 411e910e1af1..c6e383539a82 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -28,6 +28,7 @@ import android.animation.ObjectAnimator; import android.animation.TypeEvaluator; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.graphics.Insets; import android.graphics.Rect; import android.os.RemoteException; @@ -145,7 +146,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation controller.setInsetsAndAlpha( value, 1f /* alpha */, (((DefaultAnimationControlListener) ((InsetsAnimationControlImpl) controller).getListener()) - .getRawProgress())); + .getRawFraction())); } } @@ -204,9 +205,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation mController.finish(mShow); } - protected float getRawProgress() { - float fraction = (float) mAnimator.getCurrentPlayTime() / mAnimator.getDuration(); - return mShow ? fraction : 1 - fraction; + protected float getRawFraction() { + return (float) mAnimator.getCurrentPlayTime() / mAnimator.getDuration(); } protected long getDurationMs() { @@ -437,27 +437,29 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @Override public void controlWindowInsetsAnimation(@InsetsType int types, long durationMs, - WindowInsetsAnimationControlListener listener) { - controlWindowInsetsAnimation(types, listener, false /* fromIme */, durationMs, + @Nullable Interpolator interpolator, + @NonNull WindowInsetsAnimationControlListener listener) { + controlWindowInsetsAnimation(types, listener, false /* fromIme */, durationMs, interpolator, ANIMATION_TYPE_USER); } private void controlWindowInsetsAnimation(@InsetsType int types, WindowInsetsAnimationControlListener listener, boolean fromIme, long durationMs, - @AnimationType int animationType) { + @Nullable Interpolator interpolator, @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)) { listener.onCancelled(); return; } - controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs, false /* fade */, - animationType, getLayoutInsetsDuringAnimationMode(types)); + controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs, interpolator, + false /* fade */, animationType, getLayoutInsetsDuringAnimationMode(types)); } private void controlAnimationUnchecked(@InsetsType int types, WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme, - long durationMs, boolean fade, @AnimationType int animationType, + long durationMs, Interpolator interpolator, boolean fade, + @AnimationType int animationType, @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) { if (types == 0) { // nothing to animate. @@ -488,7 +490,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(controls, - frame, mState, listener, typesReady, this, durationMs, fade, + frame, mState, listener, typesReady, this, durationMs, interpolator, fade, layoutInsetsDuringAnimation); mRunningAnimations.add(new RunningAnimation(controller, animationType)); } @@ -733,7 +735,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation // and hidden state insets are correct. controlAnimationUnchecked( types, listener, mState.getDisplayFrame(), fromIme, listener.getDurationMs(), - true /* fade */, show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE, + INTERPOLATOR, 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/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java index 4f8aecd08f6d..71cf051a4e08 100644 --- a/core/java/android/view/SurfaceControlViewHost.java +++ b/core/java/android/view/SurfaceControlViewHost.java @@ -20,15 +20,21 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; import android.content.Context; +import android.graphics.PixelFormat; import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; /** - * Utility class for adding a view hierarchy to a SurfaceControl. - * - * See WindowlessWmTest for example usage. - * @hide + * Utility class for adding a View hierarchy to a {@link SurfaceControl}. The View hierarchy + * will render in to a root SurfaceControl, and receive input based on the SurfaceControl's + * placement on-screen. The primary usage of this class is to embed a View hierarchy from + * one process in to another. After the SurfaceControlViewHost has been set up in the embedded + * content provider, we can send the {@link SurfaceControlViewHost.SurfacePackage} + * to the host process. The host process can then attach the hierarchy to a SurfaceView within + * its own by calling + * {@link SurfaceView#setChildSurfacePackage}. */ -@TestApi public class SurfaceControlViewHost { private ViewRootImpl mViewRoot; private WindowlessWindowManager mWm; @@ -36,20 +42,52 @@ public class SurfaceControlViewHost { private SurfaceControl mSurfaceControl; /** - * @hide + * Package encapsulating a Surface hierarchy which contains interactive view + * elements. It's expected to get this object from + * {@link SurfaceControlViewHost#getSurfacePackage} afterwards it can be embedded within + * a SurfaceView by calling {@link SurfaceView#setChildSurfacePackage}. */ - @TestApi - public class SurfacePackage { - final SurfaceControl mSurfaceControl; + public static final class SurfacePackage implements Parcelable { + private final SurfaceControl mSurfaceControl; // TODO: Accessibility ID goes here SurfacePackage(SurfaceControl sc) { mSurfaceControl = sc; } + private SurfacePackage(Parcel in) { + mSurfaceControl = new SurfaceControl(); + mSurfaceControl.readFromParcel(in); + } + + /** + * Use {@link SurfaceView#setChildSurfacePackage} or manually fix + * accessibility (see SurfaceView implementation). + * @hide + */ public @NonNull SurfaceControl getSurfaceControl() { return mSurfaceControl; } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + mSurfaceControl.writeToParcel(out, flags); + } + + public static final @NonNull Creator<SurfacePackage> CREATOR + = new Creator<SurfacePackage>() { + public SurfacePackage createFromParcel(Parcel in) { + return new SurfacePackage(in); + } + public SurfacePackage[] newArray(int size) { + return new SurfacePackage[size]; + } + }; } /** @hide */ @@ -59,17 +97,36 @@ public class SurfaceControlViewHost { mViewRoot = new ViewRootImpl(c, d, mWm); } - public SurfaceControlViewHost(@NonNull Context c, @NonNull Display d, - @Nullable IBinder hostInputToken) { + /** + * Construct a new SurfaceControlViewHost. The root Surface will be + * allocated internally and is accessible via getSurfacePackage(). + * + * The {@param hostToken} parameter, primarily used for ANR reporting, + * must be obtained from whomever will be hosting the embedded hierarchy. + * It's accessible from {@link SurfaceView#getHostToken}. + * + * @param context The Context object for your activity or application. + * @param display The Display the hierarchy will be placed on. + * @param hostToken The host token, as discussed above. + */ + public SurfaceControlViewHost(@NonNull Context context, @NonNull Display display, + @Nullable IBinder hostToken) { mSurfaceControl = new SurfaceControl.Builder() .setContainerLayer() .setName("SurfaceControlViewHost") .build(); - mWm = new WindowlessWindowManager(c.getResources().getConfiguration(), mSurfaceControl, - hostInputToken); - mViewRoot = new ViewRootImpl(c, d, mWm); + mWm = new WindowlessWindowManager(context.getResources().getConfiguration(), + mSurfaceControl, hostToken); + mViewRoot = new ViewRootImpl(context, display, mWm); } + /** + * Return a SurfacePackage for the root SurfaceControl of the embedded hierarchy. + * Rather than be directly reparented using {@link SurfaceControl.Transaction} this + * SurfacePackage should be passed to {@link SurfaceView#setChildSurfacePackage} + * which will not only reparent the Surface, but ensure the accessibility hierarchies + * are linked. + */ public @Nullable SurfacePackage getSurfacePackage() { if (mSurfaceControl != null) { return new SurfacePackage(mSurfaceControl); @@ -78,10 +135,32 @@ public class SurfaceControlViewHost { } } - public void addView(View view, WindowManager.LayoutParams attrs) { + /** + * @hide + */ + public void addView(@NonNull View view, WindowManager.LayoutParams attrs) { mViewRoot.setView(view, attrs, null); } + /** + * Set the root view of the SurfaceControlViewHost. This view will render in to + * the SurfaceControl, and receive input based on the SurfaceControls positioning on + * screen. It will be laid as if it were in a window of the passed in width and height. + * + * @param view The View to add + * @param width The width to layout the View within, in pixels. + * @param height The height to layout the View within, in pixels. + */ + public void addView(@NonNull View view, int width, int height) { + final WindowManager.LayoutParams lp = + new WindowManager.LayoutParams(width, height, + WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.TRANSPARENT); + addView(view, lp); + } + + /** + * @hide + */ public void relayout(WindowManager.LayoutParams attrs) { mViewRoot.setLayoutParams(attrs, false); mViewRoot.setReportNextDraw(); @@ -90,8 +169,27 @@ public class SurfaceControlViewHost { }); } - public void dispose() { + /** + * Modify the size of the root view. + * + * @param width Width in pixels + * @param height Height in pixels + */ + public void relayout(int width, int height) { + final WindowManager.LayoutParams lp = + new WindowManager.LayoutParams(width, height, + WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.TRANSPARENT); + relayout(width, height); + } + + /** + * Trigger the tear down of the embedded view hierarchy and release the SurfaceControl. + * This will result in onDispatchedFromWindow being dispatched to the embedded view hierarchy + * and render the object unusable. + */ + public void release() { mViewRoot.dispatchDetachedFromWindow(); + mSurfaceControl.release(); } /** diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 0de1a4f038ff..1981bdd93eeb 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -20,6 +20,7 @@ import static android.view.WindowManagerPolicyConstants.APPLICATION_MEDIA_OVERLA import static android.view.WindowManagerPolicyConstants.APPLICATION_MEDIA_SUBLAYER; import static android.view.WindowManagerPolicyConstants.APPLICATION_PANEL_SUBLAYER; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; @@ -43,6 +44,7 @@ import android.util.AttributeSet; import android.util.Log; import android.view.SurfaceControl.Transaction; import android.view.accessibility.AccessibilityNodeInfo; +import android.view.SurfaceControlViewHost; import com.android.internal.view.SurfaceCallbackHelper; @@ -204,6 +206,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall // The token of embedded windowless view hierarchy. private IBinder mEmbeddedViewHierarchy; + SurfaceControlViewHost.SurfacePackage mSurfacePackage; public SurfaceView(Context context) { this(context, null); @@ -877,6 +880,11 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } else { mTmpTransaction.hide(mSurfaceControl); } + + if (mSurfacePackage != null) { + reparentSurfacePackage(mTmpTransaction, mSurfacePackage); + } + updateBackgroundVisibility(mTmpTransaction); if (mUseAlpha) { mTmpTransaction.setAlpha(mSurfaceControl, alpha); @@ -1471,11 +1479,12 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } /** - * @return The token used to identify the windows input channel. - * @hide + * A token used for constructing {@link SurfaceControlViewHost}. This token should + * be passed from the host process to the client process. + * + * @return The token */ - @TestApi - public @Nullable IBinder getInputToken() { + public @Nullable IBinder getHostToken() { final ViewRootImpl viewRoot = getViewRootImpl(); if (viewRoot == null) { return null; @@ -1537,6 +1546,33 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } /** + * Display the view-hierarchy embedded within a {@link SurfaceControlViewHost.SurfacePackage} + * within this SurfaceView. If this SurfaceView is above it's host Surface (see + * {@link #setZOrderOnTop} then the embedded Surface hierarchy will be able to receive + * input. + * + * @param p The SurfacePackage to embed. + */ + public void setChildSurfacePackage(@NonNull SurfaceControlViewHost.SurfacePackage p) { + final SurfaceControl sc = p != null ? p.getSurfaceControl() : null; + final SurfaceControl lastSc = mSurfacePackage != null ? + mSurfacePackage.getSurfaceControl() : null; + if (mSurfaceControl != null && lastSc != null) { + mTmpTransaction.reparent(lastSc, null).apply(); + } else if (mSurfaceControl != null) { + reparentSurfacePackage(mTmpTransaction, p); + mTmpTransaction.apply(); + } + mSurfacePackage = p; + } + + private void reparentSurfacePackage(SurfaceControl.Transaction t, + SurfaceControlViewHost.SurfacePackage p) { + // TODO: Link accessibility IDs here. + t.reparent(p.getSurfaceControl(), mSurfaceControl); + } + + /** * Add the token of embedded view hierarchy. Set {@code null} to clear the embedded view * hierarchy. * diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 377a7644f21b..7e6ef47b2484 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -23,6 +23,10 @@ import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SI import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION; import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; import static android.view.WindowInsetsAnimationCallback.DISPATCH_MODE_CONTINUE_ON_SUBTREE; +import static android.view.WindowInsets.Type.ime; +import static android.view.WindowInsets.Type.systemBars; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED; import static java.lang.Math.max; @@ -97,6 +101,7 @@ import android.util.FloatProperty; import android.util.LayoutDirection; import android.util.Log; import android.util.LongSparseLongArray; +import android.util.Pair; import android.util.Pools.SynchronizedPool; import android.util.Property; import android.util.SparseArray; @@ -110,6 +115,7 @@ import android.view.AccessibilityIterators.ParagraphTextSegmentIterator; import android.view.AccessibilityIterators.TextSegmentIterator; import android.view.AccessibilityIterators.WordTextSegmentIterator; import android.view.ContextMenu.ContextMenuInfo; +import android.view.Window.OnContentApplyWindowInsetsListener; import android.view.WindowInsetsAnimationCallback.AnimationBounds; import android.view.WindowInsetsAnimationCallback.InsetsAnimation; import android.view.WindowInsetsAnimationCallback.DispatchMode; @@ -140,6 +146,7 @@ import android.widget.FrameLayout; import android.widget.ScrollBarDrawable; import com.android.internal.R; +import com.android.internal.policy.DecorView; import com.android.internal.view.TooltipPopup; import com.android.internal.view.menu.MenuBuilder; import com.android.internal.widget.ScrollBarUtils; @@ -1510,6 +1517,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Set for framework elements that use FITS_SYSTEM_WINDOWS, to indicate * that they are optional and should be skipped if the window has * requested system UI flags that ignore those insets for layout. + * <p> + * This is only used for support library as of Android R. The framework now uses + * {@link #PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS} such that it can skip the legacy + * insets path that loses insets information. */ static final int OPTIONAL_FITS_SYSTEM_WINDOWS = 0x00000800; @@ -2258,7 +2269,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * be extended in the future to hold our own class with more than just * a Rect. :) */ - static final ThreadLocal<Rect> sThreadLocal = new ThreadLocal<Rect>(); + static final ThreadLocal<Rect> sThreadLocal = ThreadLocal.withInitial(Rect::new); /** * Map used to store views' tags. @@ -3420,6 +3431,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * 1 PFLAG4_CONTENT_CAPTURE_IMPORTANCE_IS_CACHED * 1 PFLAG4_CONTENT_CAPTURE_IMPORTANCE_CACHED_VALUE * 11 PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK + * 1 PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS * |-------|-------|-------|-------| */ @@ -3457,6 +3469,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, PFLAG4_CONTENT_CAPTURE_IMPORTANCE_IS_CACHED | PFLAG4_CONTENT_CAPTURE_IMPORTANCE_CACHED_VALUE; + /** + * @see #OPTIONAL_FITS_SYSTEM_WINDOWS + */ + static final int PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS = 0x000000100; + /* End of masks for mPrivateFlags4 */ /** @hide */ @@ -11020,23 +11037,22 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private boolean fitSystemWindowsInt(Rect insets) { if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) { - mUserPaddingStart = UNDEFINED_PADDING; - mUserPaddingEnd = UNDEFINED_PADDING; Rect localInsets = sThreadLocal.get(); - if (localInsets == null) { - localInsets = new Rect(); - sThreadLocal.set(localInsets); - } boolean res = computeFitSystemWindows(insets, localInsets); - mUserPaddingLeftInitial = localInsets.left; - mUserPaddingRightInitial = localInsets.right; - internalSetPadding(localInsets.left, localInsets.top, - localInsets.right, localInsets.bottom); + applyInsets(localInsets); return res; } return false; } + private void applyInsets(Rect insets) { + mUserPaddingStart = UNDEFINED_PADDING; + mUserPaddingEnd = UNDEFINED_PADDING; + mUserPaddingLeftInitial = insets.left; + mUserPaddingRightInitial = insets.right; + internalSetPadding(insets.left, insets.top, insets.right, insets.bottom); + } + /** * Called when the view should apply {@link WindowInsets} according to its internal policy. * @@ -11063,6 +11079,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @return The supplied insets with any applied insets consumed */ public WindowInsets onApplyWindowInsets(WindowInsets insets) { + if ((mPrivateFlags4 & PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS) != 0 + && (mViewFlags & FITS_SYSTEM_WINDOWS) != 0) { + return onApplyFrameworkOptionalFitSystemWindows(insets); + } if ((mPrivateFlags3 & PFLAG3_FITTING_SYSTEM_WINDOWS) == 0) { // We weren't called from within a direct call to fitSystemWindows, // call into it as a fallback in case we're in a class that overrides it @@ -11079,6 +11099,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return insets; } + private WindowInsets onApplyFrameworkOptionalFitSystemWindows(WindowInsets insets) { + Rect localInsets = sThreadLocal.get(); + WindowInsets result = computeSystemWindowInsets(insets, localInsets); + applyInsets(localInsets); + return result; + } + /** * Set an {@link OnApplyWindowInsetsListener} to take over the policy for applying * window insets to this view. The listener's @@ -11369,16 +11396,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @return Insets that should be passed along to views under this one */ public WindowInsets computeSystemWindowInsets(WindowInsets in, Rect outLocalInsets) { - if ((mViewFlags & OPTIONAL_FITS_SYSTEM_WINDOWS) == 0 - || mAttachInfo == null - || ((mAttachInfo.mSystemUiVisibility & SYSTEM_UI_LAYOUT_FLAGS) == 0)) { + boolean isOptionalFitSystemWindows = (mViewFlags & OPTIONAL_FITS_SYSTEM_WINDOWS) != 0 + || (mPrivateFlags4 & PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS) != 0; + if (isOptionalFitSystemWindows && mAttachInfo != null) { + OnContentApplyWindowInsetsListener listener = + mAttachInfo.mContentOnApplyWindowInsetsListener; + if (listener == null) { + // The application wants to take care of fitting system window for + // the content. + outLocalInsets.setEmpty(); + return in; + } + Pair<Insets, WindowInsets> result = listener.onContentApplyWindowInsets(in); + outLocalInsets.set(result.first.toRect()); + return result.second; + } else { outLocalInsets.set(in.getSystemWindowInsetsAsRect()); return in.consumeSystemWindowInsets().inset(outLocalInsets); - } else { - // The application wants to take care of fitting system window for - // the content. - outLocalInsets.setEmpty(); - return in; } } @@ -11449,7 +11483,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * For use by PhoneWindow to make its own system window fitting optional. + * @see #OPTIONAL_FITS_SYSTEM_WINDOWS * @hide */ @UnsupportedAppUsage @@ -11458,6 +11492,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * @see #PFLAG4_OPTIONAL_FITS_SYSTEM_WINDOWS + * @hide + */ + public void makeFrameworkOptionalFitsSystemWindows() { + mPrivateFlags4 |= PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS; + } + + /** * Returns the visibility status for this view. * * @return One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}. @@ -28415,6 +28457,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * window. */ final static class AttachInfo { + interface Callbacks { void playSoundEffect(int effectId); boolean performHapticFeedback(int effectId, boolean always); @@ -28854,6 +28897,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, ContentCaptureManager mContentCaptureManager; /** + * Listener used to fit content on window level. + */ + OnContentApplyWindowInsetsListener mContentOnApplyWindowInsetsListener; + + /** * Creates a new set of attachment information with the specified * events handler and thread. * diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index e6470a7d1e27..59d6d874dc97 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -55,6 +55,7 @@ import android.util.Pools; import android.util.Pools.SynchronizedPool; import android.util.SparseArray; import android.util.SparseBooleanArray; +import android.view.Window.OnContentApplyWindowInsetsListener; import android.view.WindowInsetsAnimationCallback.AnimationBounds; import android.view.WindowInsetsAnimationCallback.DispatchMode; import android.view.WindowInsetsAnimationCallback.InsetsAnimation; @@ -1527,6 +1528,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } + /** + * @hide + */ + @Override + public void makeFrameworkOptionalFitsSystemWindows() { + super.makeFrameworkOptionalFitsSystemWindows(); + final int count = mChildrenCount; + final View[] children = mChildren; + for (int i = 0; i < count; i++) { + children[i].makeFrameworkOptionalFitsSystemWindows(); + } + } + @Override public void dispatchDisplayHint(int hint) { super.dispatchDisplayHint(hint); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 09ebd005e7ed..c6de831656ad 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -32,6 +32,8 @@ import static android.view.View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; import static android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE; import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER; import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM; +import static android.view.WindowInsets.Type.ime; +import static android.view.WindowInsets.Type.systemBars; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS; @@ -78,6 +80,7 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.FrameInfo; import android.graphics.HardwareRenderer.FrameDrawingCallback; +import android.graphics.Insets; import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.Point; @@ -122,6 +125,7 @@ import android.view.SurfaceControl.Transaction; import android.view.View.AttachInfo; import android.view.View.FocusDirection; import android.view.View.MeasureSpec; +import android.view.Window.OnContentApplyWindowInsetsListener; import android.view.WindowInsets.Type; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; @@ -755,6 +759,11 @@ public final class ViewRootImpl implements ViewParent, mActivityConfigCallback = callback; } + public void setOnContentApplyWindowInsetsListener(OnContentApplyWindowInsetsListener listener) { + mAttachInfo.mContentOnApplyWindowInsetsListener = listener; + requestFitSystemWindows(); + } + public void addWindowCallbacks(WindowCallbacks callback) { synchronized (mWindowCallbacks) { mWindowCallbacks.add(callback); @@ -1442,6 +1451,7 @@ public final class ViewRootImpl implements ViewParent, // Get new instance of display based on current display adjustments. It may be updated later // if moving between the displays also involved a configuration change. updateInternalDisplay(displayId, mView.getResources()); + mImeFocusController.onMovedToDisplay(); mAttachInfo.mDisplayState = mDisplay.getState(); // Internal state updated, now notify the view hierarchy. mView.dispatchMovedToDisplay(mDisplay, config); diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index a1894f30d6f6..0ef4e338f81c 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -33,6 +33,7 @@ import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; +import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.Drawable; @@ -46,6 +47,9 @@ import android.os.RemoteException; import android.transition.Scene; import android.transition.Transition; import android.transition.TransitionManager; +import android.util.Pair; +import android.view.View.OnApplyWindowInsetsListener; +import android.view.ViewGroup.LayoutParams; import android.view.WindowInsets.Side.InsetsSide; import android.view.WindowInsets.Type.InsetsType; import android.view.accessibility.AccessibilityEvent; @@ -692,6 +696,32 @@ public abstract class Window { int dropCountSinceLastInvocation); } + /** + * Listener for applying window insets on the content of a window in a custom way. + * + * <p>Apps may choose to implement this interface if they want to apply custom policy + * to the way that window insets are treated for fitting root-level content views. + * + * @see Window#setOnContentApplyWindowInsetsListener(OnContentApplyWindowInsetsListener) + */ + public interface OnContentApplyWindowInsetsListener { + + /** + * Called when the window needs to apply insets on the container of its content view which + * are set by calling {@link #setContentView}. The method should determine what insets to + * apply on the container of the root level content view and what should be dispatched to + * the content view's + * {@link View#setOnApplyWindowInsetsListener(OnApplyWindowInsetsListener)} through the view + * hierarchy. + * + * @param insets The root level insets that are about to be dispatched + * @return A pair, with the first element containing the insets to apply as margin to the + * root-level content views, and the second element determining what should be + * dispatched to the content view. + */ + @NonNull Pair<Insets, WindowInsets> onContentApplyWindowInsets( + @NonNull WindowInsets insets); + } public Window(Context context) { mContext = context; @@ -1281,57 +1311,33 @@ public abstract class Window { } /** - * A shortcut for {@link WindowManager.LayoutParams#setFitWindowInsetsTypes(int)} - * @hide pending unhide - */ - public void setFitWindowInsetsTypes(@InsetsType int types) { - final WindowManager.LayoutParams attrs = getAttributes(); - attrs.setFitWindowInsetsTypes(types); - dispatchWindowAttributesChanged(attrs); - } - - /** - * A shortcut for {@link WindowManager.LayoutParams#setFitWindowInsetsSides(int)} - * @hide pending unhide - */ - public void setFitWindowInsetsSides(@InsetsSide int sides) { - final WindowManager.LayoutParams attrs = getAttributes(); - attrs.setFitWindowInsetsSides(sides); - dispatchWindowAttributesChanged(attrs); - } - - /** - * A shortcut for {@link WindowManager.LayoutParams#setFitIgnoreVisibility(boolean)} - * @hide pending unhide - */ - public void setFitIgnoreVisibility(boolean ignore) { - final WindowManager.LayoutParams attrs = getAttributes(); - attrs.setFitIgnoreVisibility(ignore); - dispatchWindowAttributesChanged(attrs); - } - - /** - * A shortcut for {@link WindowManager.LayoutParams#getFitWindowInsetsTypes} - * @hide pending unhide - */ - public @InsetsType int getFitWindowInsetsTypes() { - return getAttributes().getFitWindowInsetsTypes(); - } - - /** - * A shortcut for {@link WindowManager.LayoutParams#getFitWindowInsetsSides()} - * @hide pending unhide + * Sets the listener to be invoked when fitting root-level content views. + * <p> + * By default, a listener that inspects the now deprecated {@link View#SYSTEM_UI_LAYOUT_FLAGS} + * as well the {@link WindowManager.LayoutParams#SOFT_INPUT_ADJUST_RESIZE} flag is installed and + * fits content according to these flags. + * </p> + * @param contentOnApplyWindowInsetsListener The listener to use for fitting root-level content + * views, or {@code null} to disable any kind of + * content fitting on the window level and letting the + * {@link WindowInsets} pass through to the content + * view. + * @see OnContentApplyWindowInsetsListener */ - public @InsetsSide int getFitWindowInsetsSides() { - return getAttributes().getFitWindowInsetsSides(); + public void setOnContentApplyWindowInsetsListener( + @Nullable OnContentApplyWindowInsetsListener contentOnApplyWindowInsetsListener) { } /** - * A shortcut for {@link WindowManager.LayoutParams#getFitIgnoreVisibility()} - * @hide pending unhide + * Resets the listener set via {@link #setOnContentApplyWindowInsetsListener} to the default + * state. + * <p> + * By default, a listener that inspects the now deprecated {@link View#SYSTEM_UI_LAYOUT_FLAGS} + * as well the {@link WindowManager.LayoutParams#SOFT_INPUT_ADJUST_RESIZE} flag is installed and + * fits content according to these flags. + * </p> */ - public boolean getFitIgnoreVisibility() { - return getAttributes().getFitIgnoreVisibility(); + public void resetOnContentApplyWindowInsetsListener() { } /** diff --git a/core/java/android/view/WindowInsetsAnimationCallback.java b/core/java/android/view/WindowInsetsAnimationCallback.java index 53d493985b32..1e04d02fcb80 100644 --- a/core/java/android/view/WindowInsetsAnimationCallback.java +++ b/core/java/android/view/WindowInsetsAnimationCallback.java @@ -88,7 +88,7 @@ public interface WindowInsetsAnimationCallback { * <ul> * <li>Application calls {@link WindowInsetsController#hideInputMethod()}, * {@link WindowInsetsController#showInputMethod()}, - * {@link WindowInsetsController#controlInputMethodAnimation(long, WindowInsetsAnimationControlListener)}</li> + * {@link WindowInsetsController#controlInputMethodAnimation}</li> * <li>onPrepare is called on the view hierarchy listeners</li> * <li>{@link View#onApplyWindowInsets} will be called with the end state of the * animation</li> @@ -182,14 +182,26 @@ public interface WindowInsetsAnimationCallback { private final @InsetsType int mTypeMask; private float mFraction; @Nullable private final Interpolator mInterpolator; - private long mDurationMs; + private final long mDurationMillis; private float mAlpha; + /** + * Creates a new {@link InsetsAnimation} object. + * <p> + * This should only be used for testing, as usually the system creates this object for the + * application to listen to with {@link WindowInsetsAnimationCallback}. + * </p> + * @param typeMask The bitmask of {@link WindowInsets.Type}s that are animating. + * @param interpolator The interpolator of the animation. + * @param durationMillis The duration of the animation in + * {@link java.util.concurrent.TimeUnit#MILLISECONDS}. + */ public InsetsAnimation( - @InsetsType int typeMask, @Nullable Interpolator interpolator, long durationMs) { + @InsetsType int typeMask, @Nullable Interpolator interpolator, + long durationMillis) { mTypeMask = typeMask; mInterpolator = interpolator; - mDurationMs = durationMs; + mDurationMillis = durationMillis; } /** @@ -201,14 +213,18 @@ public interface WindowInsetsAnimationCallback { /** * Returns the raw fractional progress of this animation between - * {@link AnimationBounds#getLowerBound()} and {@link AnimationBounds#getUpperBound()}. Note + * start state of the animation and the end state of the animation. Note * that this progress is the global progress of the animation, whereas * {@link WindowInsetsAnimationCallback#onProgress} will only dispatch the insets that may * be inset with {@link WindowInsets#inset} by parents of views in the hierarchy. * Progress per insets animation is global for the entire animation. One animation animates * all things together (in, out, ...). If they don't animate together, we'd have * multiple animations. - * + * <p> + * Note: In case the application is controlling the animation, the valued returned here will + * be the same as the application passed into + * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)}. + * </p> * @return The current progress of this animation. */ @FloatRange(from = 0f, to = 1f) @@ -218,16 +234,27 @@ public interface WindowInsetsAnimationCallback { /** * Returns the interpolated fractional progress of this animation between - * {@link AnimationBounds#getLowerBound()} and {@link AnimationBounds#getUpperBound()}. Note + * start state of the animation and the end state of the animation. Note * that this progress is the global progress of the animation, whereas * {@link WindowInsetsAnimationCallback#onProgress} will only dispatch the insets that may * be inset with {@link WindowInsets#inset} by parents of views in the hierarchy. * Progress per insets animation is global for the entire animation. One animation animates * all things together (in, out, ...). If they don't animate together, we'd have * multiple animations. + * <p> + * Note: In case the application is controlling the animation, the valued returned here will + * be the same as the application passed into + * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)}, + * interpolated with the interpolator passed into + * {@link WindowInsetsController#controlInputMethodAnimation}. + * </p> + * <p> + * Note: For system-initiated animations, this will always return a valid value between 0 + * and 1. + * </p> * @see #getFraction() for raw fraction. * @return The current interpolated progress of this animation. -1 if interpolator isn't - * specified. + * specified. */ public float getInterpolatedFraction() { if (mInterpolator != null) { @@ -236,52 +263,66 @@ public interface WindowInsetsAnimationCallback { return -1; } + /** + * Retrieves the interpolator used for this animation, or {@code null} if this animation + * doesn't follow an interpolation curved. For system-initiated animations, this will never + * return {@code null}. + * + * @return The interpolator used for this animation. + */ @Nullable public Interpolator getInterpolator() { return mInterpolator; } /** - * @return duration of animation in {@link java.util.concurrent.TimeUnit#MILLISECONDS}. + * @return duration of animation in {@link java.util.concurrent.TimeUnit#MILLISECONDS}, or + * -1 if the animation doesn't have a fixed duration. */ public long getDurationMillis() { - return mDurationMs; + return mDurationMillis; } /** * Set fraction of the progress if {@link WindowInsets.Type.InsetsType} animation is - * controlled by the app {@see #getCurrentFraction}. - * <p>Note: If app didn't create {@link InsetsAnimation}, it shouldn't set progress either. - * Progress would be set by system with the system-default animation. + * controlled by the app. + * <p> + * Note: This should only be used for testing, as the system fills in the fraction for the + * application or the fraction that was passed into + * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)} is being + * used. * </p> * @param fraction fractional progress between 0 and 1 where 0 represents hidden and * zero progress and 1 represent fully shown final state. + * @see #getFraction() */ public void setFraction(@FloatRange(from = 0f, to = 1f) float fraction) { mFraction = fraction; } /** - * Set duration of the animation if {@link WindowInsets.Type.InsetsType} animation is - * controlled by the app. - * <p>Note: If app didn't create {@link InsetsAnimation}, it shouldn't set duration either. - * Duration would be set by system with the system-default animation. - * </p> - * @param durationMs in {@link java.util.concurrent.TimeUnit#MILLISECONDS} - */ - public void setDuration(long durationMs) { - mDurationMs = durationMs; - } - - /** - * @return alpha of {@link WindowInsets.Type.InsetsType}. + * Retrieves the translucency of the windows that are animating. + * + * @return Alpha of windows that cause insets of type {@link WindowInsets.Type.InsetsType}. */ @FloatRange(from = 0f, to = 1f) public float getAlpha() { return mAlpha; } - void setAlpha(@FloatRange(from = 0f, to = 1f) float alpha) { + /** + * Sets the translucency of the windows that are animating. + * <p> + * Note: This should only be used for testing, as the system fills in the alpha for the + * application or the alpha that was passed into + * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)} is being + * used. + * </p> + * @param alpha Alpha of windows that cause insets of type + * {@link WindowInsets.Type.InsetsType}. + * @see #getAlpha() + */ + public void setAlpha(@FloatRange(from = 0f, to = 1f) float alpha) { mAlpha = alpha; } } diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java index f292ca4facbf..3bb6cfbbd7f2 100644 --- a/core/java/android/view/WindowInsetsController.java +++ b/core/java/android/view/WindowInsetsController.java @@ -20,8 +20,11 @@ import static android.view.WindowInsets.Type.ime; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.graphics.Insets; import android.view.WindowInsets.Type.InsetsType; +import android.view.WindowInsetsAnimationCallback.InsetsAnimation; +import android.view.animation.Interpolator; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -148,29 +151,51 @@ public interface WindowInsetsController { * the position of the windows in the system causing insets directly. * * @param types The {@link InsetsType}s the application has requested to control. - * @param durationMillis duration of animation in + * @param durationMillis Duration of animation in * {@link java.util.concurrent.TimeUnit#MILLISECONDS}, or -1 if the - * animation doesn't have a predetermined duration. + * animation doesn't have a predetermined duration.T his value will be + * passed to {@link InsetsAnimation#getDurationMillis()} + * @param interpolator The interpolator used for this animation, or {@code null} if this + * animation doesn't follow an interpolation curve. This value will be + * passed to {@link InsetsAnimation#getInterpolator()} and used to calculate + * {@link InsetsAnimation#getInterpolatedFraction()}. * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the * windows are ready to be controlled, among other callbacks. + * + * @see InsetsAnimation#getFraction() + * @see InsetsAnimation#getInterpolatedFraction() + * @see InsetsAnimation#getInterpolator() + * @see InsetsAnimation#getDurationMillis() * @hide */ void controlWindowInsetsAnimation(@InsetsType int types, long durationMillis, + @Nullable Interpolator interpolator, @NonNull WindowInsetsAnimationControlListener listener); /** * Lets the application control the animation for showing the IME in a frame-by-frame manner by * modifying the position of the IME when it's causing insets. * - * @param durationMillis duration of the animation in + * @param durationMillis Duration of the animation in * {@link java.util.concurrent.TimeUnit#MILLISECONDS}, or -1 if the - * animation doesn't have a predetermined duration. + * animation doesn't have a predetermined duration. This value will be + * passed to {@link InsetsAnimation#getDurationMillis()} + * @param interpolator The interpolator used for this animation, or {@code null} if this + * animation doesn't follow an interpolation curve. This value will be + * passed to {@link InsetsAnimation#getInterpolator()} and used to calculate + * {@link InsetsAnimation#getInterpolatedFraction()}. * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the * IME are ready to be controlled, among other callbacks. + * + * @see InsetsAnimation#getFraction() + * @see InsetsAnimation#getInterpolatedFraction() + * @see InsetsAnimation#getInterpolator() + * @see InsetsAnimation#getDurationMillis() */ default void controlInputMethodAnimation(long durationMillis, + @Nullable Interpolator interpolator, @NonNull WindowInsetsAnimationControlListener listener) { - controlWindowInsetsAnimation(ime(), durationMillis, listener); + controlWindowInsetsAnimation(ime(), durationMillis, interpolator, listener); } /** @@ -181,7 +206,7 @@ public interface WindowInsetsController { * the event by observing {@link View#onApplyWindowInsets} and checking visibility with * {@link WindowInsets#isVisible}. * - * @see #controlInputMethodAnimation(long, WindowInsetsAnimationControlListener) + * @see #controlInputMethodAnimation * @see #hideInputMethod() */ default void showInputMethod() { @@ -196,7 +221,7 @@ public interface WindowInsetsController { * the event by observing {@link View#onApplyWindowInsets} and checking visibility with * {@link WindowInsets#isVisible}. * - * @see #controlInputMethodAnimation(long, WindowInsetsAnimationControlListener) + * @see #controlInputMethodAnimation * @see #showInputMethod() */ default void hideInputMethod() { diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index cd9dee4f7329..e768b0ffdd30 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -421,7 +421,9 @@ public interface WindowManager extends ViewManager { * </p> * * @return The display that this window manager is managing. + * @deprecated Use {@link Context#getDisplay()} instead. */ + @Deprecated public Display getDefaultDisplay(); /** @@ -1878,13 +1880,6 @@ public interface WindowManager extends ViewManager { public static final int PRIVATE_FLAG_FIT_INSETS_CONTROLLED = 0x10000000; /** - * Flag to indicate that the window only draws the bottom bar background so that we don't - * extend it to system bar areas at other sides. - * @hide - */ - public static final int PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND = 0x20000000; - - /** * An internal annotation for flags that can be specified to {@link #softInputMode}. * * @hide @@ -1994,11 +1989,7 @@ public interface WindowManager extends ViewManager { @ViewDebug.FlagToString( mask = PRIVATE_FLAG_FIT_INSETS_CONTROLLED, equals = PRIVATE_FLAG_FIT_INSETS_CONTROLLED, - name = "FIT_INSETS_CONTROLLED"), - @ViewDebug.FlagToString( - mask = PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND, - equals = PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND, - name = "ONLY_DRAW_BOTTOM_BAR_BACKGROUND") + name = "FIT_INSETS_CONTROLLED") }) @TestApi public int privateFlags; diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java index a26243c7cad5..c80a1ae55228 100644 --- a/core/java/android/view/inputmethod/EditorInfo.java +++ b/core/java/android/view/inputmethod/EditorInfo.java @@ -18,6 +18,8 @@ package android.view.inputmethod; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; +import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.os.Bundle; @@ -28,7 +30,13 @@ import android.os.UserHandle; import android.text.InputType; import android.text.TextUtils; import android.util.Printer; +import android.view.View; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Arrays; /** @@ -491,6 +499,238 @@ public class EditorInfo implements InputType, Parcelable { @Nullable public UserHandle targetInputMethodUser = null; + @IntDef({TrimPolicy.HEAD, TrimPolicy.TAIL}) + @Retention(RetentionPolicy.SOURCE) + @interface TrimPolicy { + int HEAD = 0; + int TAIL = 1; + } + + /** + * The maximum length of initialSurroundingText. When the input text from + * {@code setInitialSurroundingText(CharSequence)} is longer than this, trimming shall be + * performed to keep memory efficiency. + */ + @VisibleForTesting + static final int MEMORY_EFFICIENT_TEXT_LENGTH = 2048; + /** + * When the input text is longer than {@code #MEMORY_EFFICIENT_TEXT_LENGTH}, we start trimming + * the input text into three parts: BeforeCursor, Selection, and AfterCursor. We don't want to + * trim the Selection but we also don't want it consumes all available space. Therefore, the + * maximum acceptable Selection length is half of {@code #MEMORY_EFFICIENT_TEXT_LENGTH}. + */ + @VisibleForTesting + static final int MAX_INITIAL_SELECTION_LENGTH = MEMORY_EFFICIENT_TEXT_LENGTH / 2; + + @NonNull + private InitialSurroundingText mInitialSurroundingText = new InitialSurroundingText(); + + /** + * Editors may use this method to provide initial input text to IMEs. As the surrounding text + * could be used to provide various input assistance, we recommend editors to provide the + * complete initial input text in its {@link View#onCreateInputConnection(EditorInfo)} callback. + * The supplied text will then be processed to serve {@code #getInitialTextBeforeCursor}, + * {@code #getInitialSelectedText}, and {@code #getInitialTextBeforeCursor}. System is allowed + * to trim {@code sourceText} for various reasons while keeping the most valuable data to IMEs. + * + * <p><strong>Editor authors: </strong>Providing the initial input text helps reducing IPC calls + * for IMEs to provide many modern features right after the connection setup. We recommend + * calling this method in your implementation. + * + * @param sourceText The complete input text. + */ + public void setInitialSurroundingText(@NonNull CharSequence sourceText) { + setInitialSurroundingSubText(sourceText, /* subTextStart = */ 0); + } + + /** + * Editors may use this method to provide initial input text to IMEs. As the surrounding text + * could be used to provide various input assistance, we recommend editors to provide the + * complete initial input text in its {@link View#onCreateInputConnection(EditorInfo)} callback. + * When trimming the input text is needed, call this method instead of + * {@code setInitialSurroundingText(CharSequence)} and provide the trimmed position info. Always + * try to include the selected text within {@code subText} to give the system best flexibility + * to choose where and how to trim {@code subText} when necessary. + * + * @param subText The input text. When it was trimmed, {@code subTextStart} must be provided + * correctly. + * @param subTextStart The position that the input text got trimmed. For example, when the + * editor wants to trim out the first 10 chars, subTextStart should be 10. + */ + public void setInitialSurroundingSubText(@NonNull CharSequence subText, int subTextStart) { + Preconditions.checkNotNull(subText); + + // Swap selection start and end if necessary. + final int subTextSelStart = initialSelStart > initialSelEnd + ? initialSelEnd - subTextStart : initialSelStart - subTextStart; + final int subTextSelEnd = initialSelStart > initialSelEnd + ? initialSelStart - subTextStart : initialSelEnd - subTextStart; + + final int subTextLength = subText.length(); + // Unknown or invalid selection. + if (subTextStart < 0 || subTextSelStart < 0 || subTextSelEnd > subTextLength) { + mInitialSurroundingText = new InitialSurroundingText(); + return; + } + + // For privacy protection reason, we don't carry password inputs to IMEs. + if (isPasswordInputType(inputType)) { + mInitialSurroundingText = new InitialSurroundingText(); + return; + } + + if (subTextLength <= MEMORY_EFFICIENT_TEXT_LENGTH) { + mInitialSurroundingText = new InitialSurroundingText(subText, subTextSelStart, + subTextSelEnd); + return; + } + + // The input text is too long. Let's try to trim it reasonably. Fundamental rules are: + // 1. Text before the cursor is the most important information to IMEs. + // 2. Text after the cursor is the second important information to IMEs. + // 3. Selected text is the least important information but it shall NEVER be truncated. + // When it is too long, just drop it. + // + // Source: <TextBeforeCursor><Selection><TextAfterCursor> + // Possible results: + // 1. <(maybeTrimmedAtHead)TextBeforeCursor><Selection><TextAfterCursor(maybeTrimmedAtTail)> + // 2. <(maybeTrimmedAtHead)TextBeforeCursor><TextAfterCursor(maybeTrimmedAtTail)> + // + final int sourceSelLength = subTextSelEnd - subTextSelStart; + // When the selected text is too long, drop it. + final int newSelLength = (sourceSelLength > MAX_INITIAL_SELECTION_LENGTH) + ? 0 : sourceSelLength; + + // Distribute rest of length quota to TextBeforeCursor and TextAfterCursor in 4:1 ratio. + final int subTextBeforeCursorLength = subTextSelStart; + final int subTextAfterCursorLength = subTextLength - subTextSelEnd; + final int maxLengthMinusSelection = MEMORY_EFFICIENT_TEXT_LENGTH - newSelLength; + final int possibleMaxBeforeCursorLength = + Math.min(subTextBeforeCursorLength, (int) (0.8 * maxLengthMinusSelection)); + int newAfterCursorLength = Math.min(subTextAfterCursorLength, + maxLengthMinusSelection - possibleMaxBeforeCursorLength); + int newBeforeCursorLength = Math.min(subTextBeforeCursorLength, + maxLengthMinusSelection - newAfterCursorLength); + + // As trimming may happen at the head of TextBeforeCursor, calculate new starting position. + int newBeforeCursorHead = subTextBeforeCursorLength - newBeforeCursorLength; + + // We don't want to cut surrogate pairs in the middle. Exam that at the new head and tail. + if (isCutOnSurrogate(subText, + subTextSelStart - newBeforeCursorLength, TrimPolicy.HEAD)) { + newBeforeCursorHead = newBeforeCursorHead + 1; + newBeforeCursorLength = newBeforeCursorLength - 1; + } + if (isCutOnSurrogate(subText, + subTextSelEnd + newAfterCursorLength - 1, TrimPolicy.TAIL)) { + newAfterCursorLength = newAfterCursorLength - 1; + } + + // Now we know where to trim, compose the initialSurroundingText. + final int newTextLength = newBeforeCursorLength + newSelLength + newAfterCursorLength; + CharSequence newInitialSurroundingText; + if (newSelLength != sourceSelLength) { + final CharSequence beforeCursor = subText.subSequence(newBeforeCursorHead, + newBeforeCursorHead + newBeforeCursorLength); + + final CharSequence afterCursor = subText.subSequence(subTextSelEnd, + subTextSelEnd + newAfterCursorLength); + + newInitialSurroundingText = TextUtils.concat(beforeCursor, afterCursor); + } else { + newInitialSurroundingText = subText + .subSequence(newBeforeCursorHead, newBeforeCursorHead + newTextLength); + } + + // As trimming may happen at the head, adjust cursor position in the initialSurroundingText + // obj. + newBeforeCursorHead = 0; + final int newSelHead = newBeforeCursorHead + newBeforeCursorLength; + mInitialSurroundingText = new InitialSurroundingText( + newInitialSurroundingText, newSelHead, newSelHead + newSelLength); + } + + /** + * Get <var>n</var> characters of text before the current cursor position. May be {@code null} + * when the protocol is not supported. + * + * @param length The expected length of the text. + * @param flags Supplies additional options controlling how the text is returned. May be + * either 0 or {@link InputConnection#GET_TEXT_WITH_STYLES}. + * @return the text before the cursor position; the length of the returned text might be less + * than <var>n</var>. When there is no text before the cursor, an empty string will be returned. + * It could also be {@code null} when the editor or system could not support this protocol. + */ + @Nullable + public CharSequence getInitialTextBeforeCursor(int length, int flags) { + return mInitialSurroundingText.getInitialTextBeforeCursor(length, flags); + } + + /** + * Gets the selected text, if any. May be {@code null} when no text is selected or the selected + * text is way too long. + * + * @param flags Supplies additional options controlling how the text is returned. May be + * either 0 or {@link InputConnection#GET_TEXT_WITH_STYLES}. + * @return the text that is currently selected, if any. It could be an empty string when there + * is no text selected. When {@code null} is returned, the selected text might be too long or + * this protocol is not supported. + */ + @Nullable + public CharSequence getInitialSelectedText(int flags) { + // Swap selection start and end if necessary. + final int correctedTextSelStart = initialSelStart > initialSelEnd + ? initialSelEnd : initialSelStart; + final int correctedTextSelEnd = initialSelStart > initialSelEnd + ? initialSelStart : initialSelEnd; + + final int sourceSelLength = correctedTextSelEnd - correctedTextSelStart; + if (initialSelStart < 0 || initialSelEnd < 0 + || mInitialSurroundingText.getSelectionLength() != sourceSelLength) { + return null; + } + return mInitialSurroundingText.getInitialSelectedText(flags); + } + + /** + * Get <var>n</var> characters of text after the current cursor position. May be {@code null} + * when the protocol is not supported. + * + * @param length The expected length of the text. + * @param flags Supplies additional options controlling how the text is returned. May be + * either 0 or {@link InputConnection#GET_TEXT_WITH_STYLES}. + * @return the text after the cursor position; the length of the returned text might be less + * than <var>n</var>. When there is no text after the cursor, an empty string will be returned. + * It could also be {@code null} when the editor or system could not support this protocol. + */ + @Nullable + public CharSequence getInitialTextAfterCursor(int length, int flags) { + return mInitialSurroundingText.getInitialTextAfterCursor(length, flags); + } + + private static boolean isCutOnSurrogate(CharSequence sourceText, int cutPosition, + @TrimPolicy int policy) { + switch (policy) { + case TrimPolicy.HEAD: + return Character.isLowSurrogate(sourceText.charAt(cutPosition)); + case TrimPolicy.TAIL: + return Character.isHighSurrogate(sourceText.charAt(cutPosition)); + default: + return false; + } + } + + private static boolean isPasswordInputType(int inputType) { + final int variation = + inputType & (TYPE_MASK_CLASS | TYPE_MASK_VARIATION); + return variation + == (TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_PASSWORD) + || variation + == (TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_WEB_PASSWORD) + || variation + == (TYPE_CLASS_NUMBER | TYPE_NUMBER_VARIATION_PASSWORD); + } + /** * Ensure that the data in this EditorInfo is compatible with an application * that was developed against the given target API version. This can @@ -573,6 +813,7 @@ public class EditorInfo implements InputType, Parcelable { dest.writeInt(fieldId); dest.writeString(fieldName); dest.writeBundle(extras); + mInitialSurroundingText.writeToParcel(dest, flags); if (hintLocales != null) { hintLocales.writeToParcel(dest, flags); } else { @@ -603,6 +844,9 @@ public class EditorInfo implements InputType, Parcelable { res.fieldId = source.readInt(); res.fieldName = source.readString(); res.extras = source.readBundle(); + InitialSurroundingText initialSurroundingText = + InitialSurroundingText.CREATOR.createFromParcel(source); + res.mInitialSurroundingText = initialSurroundingText; LocaleList hintLocales = LocaleList.CREATOR.createFromParcel(source); res.hintLocales = hintLocales.isEmpty() ? null : hintLocales; res.contentMimeTypes = source.readStringArray(); @@ -619,4 +863,93 @@ public class EditorInfo implements InputType, Parcelable { return 0; } + // TODO(b/148035211): Unit tests for this class + static final class InitialSurroundingText implements Parcelable { + @Nullable final CharSequence mSurroundingText; + final int mSelectionHead; + final int mSelectionEnd; + + InitialSurroundingText() { + mSurroundingText = null; + mSelectionHead = 0; + mSelectionEnd = 0; + } + + InitialSurroundingText(@Nullable CharSequence surroundingText, int selectionHead, + int selectionEnd) { + mSurroundingText = surroundingText; + mSelectionHead = selectionHead; + mSelectionEnd = selectionEnd; + } + + @Nullable + private CharSequence getInitialTextBeforeCursor(int n, int flags) { + if (mSurroundingText == null) { + return null; + } + + final int length = Math.min(n, mSelectionHead); + return ((flags & InputConnection.GET_TEXT_WITH_STYLES) != 0) + ? mSurroundingText.subSequence(mSelectionHead - length, mSelectionHead) + : TextUtils.substring(mSurroundingText, mSelectionHead - length, + mSelectionHead); + } + + @Nullable + private CharSequence getInitialSelectedText(int flags) { + if (mSurroundingText == null) { + return null; + } + + return ((flags & InputConnection.GET_TEXT_WITH_STYLES) != 0) + ? mSurroundingText.subSequence(mSelectionHead, mSelectionEnd) + : TextUtils.substring(mSurroundingText, mSelectionHead, mSelectionEnd); + } + + @Nullable + private CharSequence getInitialTextAfterCursor(int n, int flags) { + if (mSurroundingText == null) { + return null; + } + + final int length = Math.min(n, mSurroundingText.length() - mSelectionEnd); + return ((flags & InputConnection.GET_TEXT_WITH_STYLES) != 0) + ? mSurroundingText.subSequence(mSelectionEnd, mSelectionEnd + length) + : TextUtils.substring(mSurroundingText, mSelectionEnd, mSelectionEnd + length); + } + + private int getSelectionLength() { + return mSelectionEnd - mSelectionHead; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + TextUtils.writeToParcel(mSurroundingText, dest, flags); + dest.writeInt(mSelectionHead); + dest.writeInt(mSelectionEnd); + } + + public static final @android.annotation.NonNull Parcelable.Creator<InitialSurroundingText> + CREATOR = new Parcelable.Creator<InitialSurroundingText>() { + @Override + public InitialSurroundingText createFromParcel(Parcel source) { + final CharSequence initialText = + TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); + final int selectionHead = source.readInt(); + final int selectionEnd = source.readInt(); + + return new InitialSurroundingText(initialText, selectionHead, selectionEnd); + } + + @Override + public InitialSurroundingText[] newArray(int size) { + return new InitialSurroundingText[size]; + } + }; + } } diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java index e5545405728d..4337ed5109db 100644 --- a/core/java/android/view/inputmethod/InputConnection.java +++ b/core/java/android/view/inputmethod/InputConnection.java @@ -158,7 +158,11 @@ public interface InputConnection { * trigger an IPC round-trip that will take some time. Assume this * method consumes a lot of time. Also, please keep in mind the * Editor may choose to return less characters than requested even - * if they are available for performance reasons.</p> + * if they are available for performance reasons. If you are using + * this to get the initial text around the cursor, you may consider + * using {@link EditorInfo#getInitialTextBeforeCursor(int, int)}, + * {@link EditorInfo#getInitialSelectedText(int)}, and + * {@link EditorInfo#getInitialTextAfterCursor(int, int)} to prevent IPC costs.</p> * * <p><strong>Editor authors:</strong> please be careful of race * conditions in implementing this call. An IME can make a change @@ -196,7 +200,11 @@ public interface InputConnection { * * <p><strong>IME authors:</strong> please consider this will * trigger an IPC round-trip that will take some time. Assume this - * method consumes a lot of time.</p> + * method consumes a lot of time. If you are using this to get the + * initial text around the cursor, you may consider using + * {@link EditorInfo#getInitialTextBeforeCursor(int, int)}, + * {@link EditorInfo#getInitialSelectedText(int)}, and + * {@link EditorInfo#getInitialTextAfterCursor(int, int)} to prevent IPC costs.</p> * * <p><strong>Editor authors:</strong> please be careful of race * conditions in implementing this call. An IME can make a change @@ -234,7 +242,11 @@ public interface InputConnection { * * <p><strong>IME authors:</strong> please consider this will * trigger an IPC round-trip that will take some time. Assume this - * method consumes a lot of time.</p> + * method consumes a lot of time. If you are using this to get the + * initial text around the cursor, you may consider using + * {@link EditorInfo#getInitialTextBeforeCursor(int, int)}, + * {@link EditorInfo#getInitialSelectedText(int)}, and + * {@link EditorInfo#getInitialTextAfterCursor(int, int)} to prevent IPC costs.</p> * * <p><strong>Editor authors:</strong> please be careful of race * conditions in implementing this call. An IME can make a change diff --git a/core/java/android/webkit/PacProcessor.java b/core/java/android/webkit/PacProcessor.java index fe8bbb8b6c82..5ef450fa65dd 100644 --- a/core/java/android/webkit/PacProcessor.java +++ b/core/java/android/webkit/PacProcessor.java @@ -54,5 +54,5 @@ public interface PacProcessor { * For example: "PROXY xxx.xxx.xxx.xxx:xx; SOCKS yyy.yyy.yyy:yy". */ @Nullable - String makeProxyRequest(@NonNull String url); + String findProxyForUrl(@NonNull String url); } diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index b10631b7ef2f..cdf8c686ef00 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -391,6 +391,9 @@ public class Editor { // This can only be true if the text view is editable. private boolean mCursorControlEnabled; + // Specifies whether the new magnifier (with fish-eye effect) is enabled. + private final boolean mNewMagnifierEnabled; + Editor(TextView textView) { mTextView = textView; // Synchronize the filter list, which places the undo input filter at the end. @@ -401,9 +404,13 @@ public class Editor { mCursorControlEnabled = AppGlobals.getIntCoreSetting( WidgetFlags.KEY_ENABLE_CURSOR_CONTROL , 0) != 0; + mNewMagnifierEnabled = AppGlobals.getIntCoreSetting( + WidgetFlags.KEY_ENABLE_NEW_MAGNIFIER, 0) != 0; if (TextView.DEBUG_CURSOR) { logCursor("Editor", "Cursor control is %s.", mCursorControlEnabled ? "enabled" : "disabled"); + logCursor("Editor", "New magnifier is %s.", + mNewMagnifierEnabled ? "enabled" : "disabled"); } } @@ -422,7 +429,7 @@ public class Editor { if (FLAG_USE_MAGNIFIER && mMagnifierAnimator == null) { // Lazy creates the magnifier instance because it requires the text height which cannot // be measured at the time of Editor instance being created. - final Magnifier.Builder builder = shouldUseNewMagnifier() + final Magnifier.Builder builder = mNewMagnifierEnabled ? createBuilderWithInlineMagnifierDefaults() : Magnifier.createBuilderWithOldMagnifierDefaults(mTextView); mMagnifierAnimator = new MagnifierMotionAnimator(builder.build()); @@ -430,24 +437,28 @@ public class Editor { return mMagnifierAnimator; } - private boolean shouldUseNewMagnifier() { - // TODO: use a separate flag to enable new magnifier. - return mCursorControlEnabled; - } - private Magnifier.Builder createBuilderWithInlineMagnifierDefaults() { final Magnifier.Builder params = new Magnifier.Builder(mTextView); // TODO: supports changing the height/width dynamically because the text height can be // dynamically changed. + float zoom = AppGlobals.getFloatCoreSetting( + WidgetFlags.KEY_MAGNIFIER_ZOOM_FACTOR, 1.5f); + float aspectRatio = AppGlobals.getFloatCoreSetting( + WidgetFlags.KEY_MAGNIFIER_ASPECT_RATIO, 5.5f); + // Avoid invalid/unsupported values. + if (zoom < 1.2f || zoom > 1.8f) { + zoom = 1.5f; + } + if (aspectRatio < 3 || aspectRatio > 8) { + aspectRatio = 5.5f; + } + final Paint.FontMetrics fontMetrics = mTextView.getPaint().getFontMetrics(); final float sourceHeight = fontMetrics.descent - fontMetrics.ascent; - final float zoom = 1.5f; - final float widthHeightRatio = 5.5f; // Slightly increase the height to avoid tooLargeTextForMagnifier() returns true. int height = (int)(sourceHeight * zoom) + 2; - int width = (int)(widthHeightRatio * height); - + int width = (int)(aspectRatio * height); params.setFishEyeStyle() .setSize(width, height) @@ -4680,11 +4691,11 @@ public class Editor { } }; - private int getPreferredWidth() { + protected final int getPreferredWidth() { return Math.max(mDrawable.getIntrinsicWidth(), mMinSize); } - private int getPreferredHeight() { + protected final int getPreferredHeight() { return Math.max(mDrawable.getIntrinsicHeight(), mMinSize); } @@ -5089,7 +5100,7 @@ public class Editor { mTextView.invalidateCursorPath(); suspendBlink(); - if (shouldUseNewMagnifier()) { + if (mNewMagnifierEnabled) { // Calculates the line bounds as the content source bounds to the magnifier. Layout layout = mTextView.getLayout(); int line = layout.getLineForOffset(getCurrentCursorOffset()); @@ -5218,7 +5229,7 @@ public class Editor { // Whether the popup window is in the invisible state and will be dismissed when finger up. private boolean mPendingDismissOnUp = false; // The alpha value of the drawable. - private final int mDrawableOpacity = 255; + private final int mDrawableOpacity; // Members for toggling the insertion menu in touch through mode. @@ -5239,8 +5250,29 @@ public class Editor { // of the touch through events. private float mTextHeight; - public InsertionHandleView(Drawable drawable) { + // The delta height applied to the insertion handle view. + private final int mDeltaHeight; + + InsertionHandleView(Drawable drawable) { super(drawable, drawable, com.android.internal.R.id.insertion_handle); + + int deltaHeight = 0; + int opacity = 255; + if (mCursorControlEnabled) { + deltaHeight = AppGlobals.getIntCoreSetting( + WidgetFlags.KEY_INSERTION_HANDLE_DELTA_HEIGHT, 25); + opacity = AppGlobals.getIntCoreSetting( + WidgetFlags.KEY_INSERTION_HANDLE_OPACITY, 50); + // Avoid invalid/unsupported values. + if (deltaHeight < -25 || deltaHeight > 50) { + deltaHeight = 25; + } + if (opacity < 10 || opacity > 100) { + opacity = 50; + } + } + mDeltaHeight = deltaHeight; + mDrawableOpacity = opacity; } private void hideAfterDelay() { @@ -5293,6 +5325,17 @@ public class Editor { } @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (mCursorControlEnabled) { + final int height = Math.max( + getPreferredHeight() + mDeltaHeight, mDrawable.getIntrinsicHeight()); + setMeasuredDimension(getPreferredWidth(), height); + return; + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + @Override public boolean onTouchEvent(MotionEvent ev) { if (mCursorControlEnabled && FLAG_ENABLE_CURSOR_DRAG) { // Should only enable touch through when cursor drag is enabled. diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java index 733a7753568a..970d70cf1fb4 100644 --- a/core/java/android/widget/ProgressBar.java +++ b/core/java/android/widget/ProgressBar.java @@ -52,7 +52,6 @@ import android.view.View; import android.view.ViewDebug; import android.view.ViewHierarchyEncoder; import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; @@ -307,6 +306,9 @@ public class ProgressBar extends View { setMax(a.getInt(R.styleable.ProgressBar_max, mMax)); setProgress(a.getInt(R.styleable.ProgressBar_progress, mProgress)); + // onProgressRefresh() is only called when the progress changes. So we should set + // stateDescription during initialization here. + super.setStateDescription(formatStateDescription(mProgress)); setSecondaryProgress(a.getInt( R.styleable.ProgressBar_secondaryProgress, mSecondaryProgress)); @@ -1599,8 +1601,7 @@ public class ProgressBar extends View { } void onProgressRefresh(float scale, boolean fromUser, int progress) { - if (AccessibilityManager.getInstance(mContext).isEnabled() - && mCustomStateDescription == null) { + if (mCustomStateDescription == null) { super.setStateDescription(formatStateDescription(mProgress)); } } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 32d3fef88e81..8ce6ee576b00 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -8668,6 +8668,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener outAttrs.initialSelStart = getSelectionStart(); outAttrs.initialSelEnd = getSelectionEnd(); outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType()); + outAttrs.setInitialSurroundingText(mText); return ic; } } diff --git a/core/java/android/widget/WidgetFlags.java b/core/java/android/widget/WidgetFlags.java index fa1e498d55b6..1a8e7a713e7a 100644 --- a/core/java/android/widget/WidgetFlags.java +++ b/core/java/android/widget/WidgetFlags.java @@ -35,6 +35,66 @@ public final class WidgetFlags { */ public static final String KEY_ENABLE_CURSOR_CONTROL = "widget__enable_cursor_control"; + /** + * The flag of delta height applies to the insertion handle when cursor control flag is enabled. + * The default value is 25. + */ + public static final String INSERTION_HANDLE_DELTA_HEIGHT = + "CursorControlFeature__insertion_handle_delta_height"; + + /** + * The key name used in app core settings for {@link #INSERTION_HANDLE_DELTA_HEIGHT}. + */ + public static final String KEY_INSERTION_HANDLE_DELTA_HEIGHT = + "widget__insertion_handle_delta_height"; + + /** + * The flag of opacity applies to the insertion handle when cursor control flag is enabled. + * The opacity value is in the range of {0..100}. The default value is 50. + */ + public static final String INSERTION_HANDLE_OPACITY = + "CursorControlFeature__insertion_handle_opacity"; + + /** + * The key name used in app core settings for {@link #INSERTION_HANDLE_OPACITY}. + */ + public static final String KEY_INSERTION_HANDLE_OPACITY = + "widget__insertion_handle_opacity"; + + /** + * The flag of enabling the new magnifier. + */ + public static final String ENABLE_NEW_MAGNIFIER = "CursorControlFeature__enable_new_magnifier"; + + /** + * The key name used in app core settings for {@link #ENABLE_NEW_MAGNIFIER}. + */ + public static final String KEY_ENABLE_NEW_MAGNIFIER = "widget__enable_new_magnifier"; + + /** + * The flag of zoom factor applies to the new magnifier. + * The default value is 1.5f. + */ + public static final String MAGNIFIER_ZOOM_FACTOR = + "CursorControlFeature__magnifier_zoom_factor"; + + /** + * The key name used in app core settings for {@link #MAGNIFIER_ZOOM_FACTOR}. + */ + public static final String KEY_MAGNIFIER_ZOOM_FACTOR = "widget__magnifier_zoom_factor"; + + /** + * The flag of aspect ratio (width/height) applies to the new magnifier. + * The default value is 5.5f. + */ + public static final String MAGNIFIER_ASPECT_RATIO = + "CursorControlFeature__magnifier_aspect_ratio"; + + /** + * The key name used in app core settings for {@link #MAGNIFIER_ASPECT_RATIO}. + */ + public static final String KEY_MAGNIFIER_ASPECT_RATIO = "widget__magnifier_aspect_ratio"; + private WidgetFlags() { } } diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index be2d1d60e9a2..f3b6d292623d 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -26,6 +26,7 @@ import com.android.internal.app.IVoiceInteractionSessionShowCallback; import com.android.internal.app.IVoiceInteractor; import com.android.internal.app.IVoiceInteractionSessionListener; import android.hardware.soundtrigger.IRecognitionStatusCallback; +import android.hardware.soundtrigger.KeyphraseMetadata; import android.hardware.soundtrigger.ModelParams; import android.hardware.soundtrigger.SoundTrigger; import android.service.voice.IVoiceInteractionService; @@ -72,8 +73,8 @@ interface IVoiceInteractionManagerService { */ SoundTrigger.ModuleProperties getDspModuleProperties(in IVoiceInteractionService service); /** - * Indicates if there's a keyphrase sound model available for the given keyphrase ID. - * This performs the check for the current user. + * Indicates if there's a keyphrase sound model available for the given keyphrase ID and the + * user ID of the caller. * * @param service The current VoiceInteractionService. * @param keyphraseId The unique identifier for the keyphrase. @@ -82,6 +83,18 @@ interface IVoiceInteractionManagerService { boolean isEnrolledForKeyphrase(IVoiceInteractionService service, int keyphraseId, String bcp47Locale); /** + * Generates KeyphraseMetadata for an enrolled sound model based on keyphrase string, locale, + * and the user ID of the caller. + * + * @param service The current VoiceInteractionService + * @param keyphrase Keyphrase text associated with the enrolled model + * @param bcp47Locale The BCP47 language tag for the keyphrase's locale. + * @return The metadata for the enrolled voice model bassed on the passed in parameters. Null if + * no matching voice model exists. + */ + KeyphraseMetadata getEnrolledKeyphraseMetadata(IVoiceInteractionService service, + String keyphrase, String bcp47Locale); + /** * Starts a recognition for the given keyphrase. */ int startRecognition(in IVoiceInteractionService service, int keyphraseId, diff --git a/core/java/com/android/internal/logging/InstanceId.java b/core/java/com/android/internal/logging/InstanceId.java new file mode 100644 index 000000000000..85dbac3f59bb --- /dev/null +++ b/core/java/com/android/internal/logging/InstanceId.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.logging; + +import android.annotation.Nullable; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * An opaque identifier used to disambiguate which logs refer to a particular instance of some + * UI element. Useful when there might be multiple instances simultaneously active. + * Obtain from InstanceIdSequence. + */ +public class InstanceId { + private int mId; + protected InstanceId(int id) { + mId = id; + } + @VisibleForTesting + public int getId() { + return mId; + } + + @Override + public int hashCode() { + return mId; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof InstanceId)) { + return false; + } + return mId == ((InstanceId) obj).mId; + } +} diff --git a/core/java/com/android/internal/logging/InstanceIdSequence.java b/core/java/com/android/internal/logging/InstanceIdSequence.java new file mode 100644 index 000000000000..2e78ed8c5185 --- /dev/null +++ b/core/java/com/android/internal/logging/InstanceIdSequence.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.logging; + +import static java.lang.Math.max; +import static java.lang.Math.min; + +import java.security.SecureRandom; +import java.util.Random; + +/** + * Generates random InstanceIds in range [0, instanceIdMax) for passing to + * UiEventLogger.logWithInstanceId(). Holds a SecureRandom, which self-seeds on + * first use; try to give it a long lifetime. Safe for concurrent use. + */ +public class InstanceIdSequence { + // At most 20 bits: ~1m possibilities, ~0.5% probability of collision in 100 values + private static final int INSTANCE_ID_MAX = 1 << 20; + protected final int mInstanceIdMax; + private final Random mRandom = new SecureRandom(); + + /** + * Constructs a sequence with identifiers [0, instanceIdMax). Capped at INSTANCE_ID_MAX. + * @param instanceIdMax Limiting value of identifiers. Normally positive: otherwise you get + * an all-zero sequence. + */ + public InstanceIdSequence(int instanceIdMax) { + mInstanceIdMax = min(max(0, instanceIdMax), INSTANCE_ID_MAX); + } + + /** + * Gets the next instance from the sequence. Safe for concurrent use. + * @return new InstanceId + */ + public InstanceId newInstanceId() { + return new InstanceId(mRandom.nextInt(mInstanceIdMax)); + } +} diff --git a/core/java/com/android/internal/logging/UiEventLogger.java b/core/java/com/android/internal/logging/UiEventLogger.java index 3a450de6c8e6..48d2bc2ae58d 100644 --- a/core/java/com/android/internal/logging/UiEventLogger.java +++ b/core/java/com/android/internal/logging/UiEventLogger.java @@ -49,4 +49,15 @@ public interface UiEventLogger { * @param packageName the package name of the relevant app, if known (null otherwise). */ void log(@NonNull UiEventEnum event, int uid, @Nullable String packageName); + + /** + * Log an event with package information and an instance ID. + * Does nothing if event.getId() <= 0. + * @param event an enum implementing UiEventEnum interface. + * @param uid the uid of the relevant app, if known (0 otherwise). + * @param packageName the package name of the relevant app, if known (null otherwise). + * @param instance An identifier obtained from an InstanceIdSequence. + */ + void logWithInstanceId(@NonNull UiEventEnum event, int uid, @Nullable String packageName, + @NonNull InstanceId instance); } diff --git a/core/java/com/android/internal/logging/UiEventLoggerImpl.java b/core/java/com/android/internal/logging/UiEventLoggerImpl.java index bdf460c710c3..fe758a8e6cb7 100644 --- a/core/java/com/android/internal/logging/UiEventLoggerImpl.java +++ b/core/java/com/android/internal/logging/UiEventLoggerImpl.java @@ -36,4 +36,14 @@ public class UiEventLoggerImpl implements UiEventLogger { StatsLog.write(StatsLog.UI_EVENT_REPORTED, eventID, uid, packageName); } } + + @Override + public void logWithInstanceId(UiEventEnum event, int uid, String packageName, + InstanceId instance) { + final int eventID = event.getId(); + if (eventID > 0) { + StatsLog.write(StatsLog.UI_EVENT_REPORTED, eventID, uid, packageName, + instance.getId()); + } + } } diff --git a/core/java/com/android/internal/logging/testing/InstanceIdSequenceFake.java b/core/java/com/android/internal/logging/testing/InstanceIdSequenceFake.java new file mode 100644 index 000000000000..0fd40b9ceb06 --- /dev/null +++ b/core/java/com/android/internal/logging/testing/InstanceIdSequenceFake.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.logging.testing; + +import android.annotation.SuppressLint; + +import com.android.internal.logging.InstanceId; +import com.android.internal.logging.InstanceIdSequence; + +/** + * A fake implementation of InstanceIdSequence that returns 0, 1, 2, ... + */ +public class InstanceIdSequenceFake extends InstanceIdSequence { + + public InstanceIdSequenceFake(int instanceIdMax) { + super(instanceIdMax); + } + + /** + * Extend InstanceId to add a constructor we can call, strictly for testing purposes. + * Public so that tests can check whether the InstanceIds they see are fake. + */ + public static class InstanceIdFake extends InstanceId { + @SuppressLint("VisibleForTests") // This is test infrastructure, which ought to count + InstanceIdFake(int id) { + super(id); + } + } + + private int mNextId = 0; + + @Override + public InstanceId newInstanceId() { + synchronized (this) { + ++mNextId; + if (mNextId >= mInstanceIdMax) { + mNextId = 0; + } + return new InstanceIdFake(mNextId); + } + } +} diff --git a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java index 6be5b81afee2..130ee64ac887 100644 --- a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java +++ b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java @@ -16,6 +16,7 @@ package com.android.internal.logging.testing; +import com.android.internal.logging.InstanceId; import com.android.internal.logging.UiEventLogger; import java.util.LinkedList; @@ -34,15 +35,24 @@ public class UiEventLoggerFake implements UiEventLogger { public final int eventId; public final int uid; public final String packageName; + public final InstanceId instanceId; // Used only for WithInstanceId variant - public FakeUiEvent(int eventId, int uid, String packageName) { + FakeUiEvent(int eventId, int uid, String packageName) { this.eventId = eventId; this.uid = uid; this.packageName = packageName; + this.instanceId = null; + } + + FakeUiEvent(int eventId, int uid, String packageName, InstanceId instanceId) { + this.eventId = eventId; + this.uid = uid; + this.packageName = packageName; + this.instanceId = instanceId; } } - private Queue<FakeUiEvent> mLogs = new LinkedList<FakeUiEvent>(); + private Queue<FakeUiEvent> mLogs = new LinkedList<>(); public Queue<FakeUiEvent> getLogs() { return mLogs; @@ -60,4 +70,13 @@ public class UiEventLoggerFake implements UiEventLogger { mLogs.offer(new FakeUiEvent(eventId, uid, packageName)); } } + + @Override + public void logWithInstanceId(UiEventLogger.UiEventEnum event, int uid, String packageName, + InstanceId instance) { + final int eventId = event.getId(); + if (eventId > 0) { + mLogs.offer(new FakeUiEvent(eventId, uid, packageName, instance)); + } + } } diff --git a/core/java/com/android/internal/os/ProcessCpuTracker.java b/core/java/com/android/internal/os/ProcessCpuTracker.java index 5e3c5dfe6bdc..7a9b6590d56b 100644 --- a/core/java/com/android/internal/os/ProcessCpuTracker.java +++ b/core/java/com/android/internal/os/ProcessCpuTracker.java @@ -753,6 +753,8 @@ public class ProcessCpuTracker { proto.write(CpuUsageProto.Load.LOAD15, mLoad15); proto.end(currentLoadToken); + buildWorkingProcs(); + proto.write(CpuUsageProto.NOW, now); proto.write(CpuUsageProto.LAST_SAMPLE_TIME, mLastSampleTime); proto.write(CpuUsageProto.CURRENT_SAMPLE_TIME, mCurrentSampleTime); diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index a6637a2fb601..8412b846b2a6 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -68,6 +68,7 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; import android.graphics.drawable.LayerDrawable; +import android.os.Build.VERSION_CODES; import android.util.DisplayMetrics; import android.util.Log; import android.util.Pair; @@ -282,6 +283,11 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind private Insets mLastBackgroundInsets = Insets.NONE; private boolean mDrawLegacyNavigationBarBackground; + /** + * Whether the app targets an SDK that uses the new insets APIs. + */ + private boolean mUseNewInsetsApi; + DecorView(Context context, int featureId, PhoneWindow window, WindowManager.LayoutParams params) { super(context); @@ -311,6 +317,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind initResizingPaints(); mLegacyNavigationBarBackgroundPaint.setColor(Color.BLACK); + mUseNewInsetsApi = context.getApplicationInfo().targetSdkVersion >= VERSION_CODES.R; } void setBackgroundFallback(@Nullable Drawable fallbackDrawable) { @@ -1174,20 +1181,24 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind // Note: We don't need to check for IN_SCREEN or INSET_DECOR because unlike the status bar, // these flags wouldn't make the window draw behind the navigation bar, unless // LAYOUT_HIDE_NAVIGATION was set. + // + // Note: Once the app targets R+, we no longer do this logic because we can't rely on + // SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION to indicate whether the app wants to handle it by + // themselves. boolean hideNavigation = (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0 || !(state == null || state.getSource(ITYPE_NAVIGATION_BAR).isVisible()); boolean forceConsumingNavBar = (mForceWindowDrawsBarBackgrounds && (attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0 && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0 - && (attrs.getFitWindowInsetsTypes() & Type.navigationBars()) != 0 && !hideNavigation) || (mLastShouldAlwaysConsumeSystemBars && hideNavigation); boolean consumingNavBar = ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0 - && (attrs.getFitWindowInsetsTypes() & Type.navigationBars()) != 0 - && !hideNavigation) + && !hideNavigation + // TODO IME wrap_content windows need to have margin to work properly + && (!mUseNewInsetsApi || isImeWindow)) || forceConsumingNavBar; // If we didn't request fullscreen layout, but we still got it because of the @@ -1200,16 +1211,14 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind boolean consumingStatusBar = (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0 && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0 && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0 - && (attrs.getFitWindowInsetsTypes() & Type.statusBars()) != 0 && mForceWindowDrawsBarBackgrounds && mLastTopInset != 0 || (mLastShouldAlwaysConsumeSystemBars && fullscreen); - int sides = attrs.getFitWindowInsetsSides(); - int consumedTop = consumingStatusBar && (sides & Side.TOP) != 0 ? mLastTopInset : 0; - int consumedRight = consumingNavBar && (sides & Side.RIGHT) != 0 ? mLastRightInset : 0; - int consumedBottom = consumingNavBar && (sides & Side.BOTTOM) != 0 ? mLastBottomInset : 0; - int consumedLeft = consumingNavBar && (sides & Side.LEFT) != 0 ? mLastLeftInset : 0; + int consumedTop = consumingStatusBar ? mLastTopInset : 0; + int consumedRight = consumingNavBar ? mLastRightInset : 0; + int consumedBottom = consumingNavBar ? mLastBottomInset : 0; + int consumedLeft = consumingNavBar ? mLastLeftInset : 0; if (mContentRoot != null && mContentRoot.getLayoutParams() instanceof MarginLayoutParams) { diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index 4abd39797ba0..cb0a985e6f11 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -17,8 +17,11 @@ package com.android.internal.policy; import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES; +import static android.view.View.SYSTEM_UI_LAYOUT_FLAGS; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import static android.view.WindowInsets.Type.ime; +import static android.view.WindowInsets.Type.systemBars; import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; @@ -30,6 +33,8 @@ import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; import android.annotation.NonNull; import android.app.ActivityManager; @@ -43,6 +48,7 @@ import android.content.res.Configuration; import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.graphics.Color; +import android.graphics.Insets; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.media.AudioManager; @@ -66,6 +72,7 @@ import android.transition.TransitionSet; import android.util.AndroidRuntimeException; import android.util.EventLog; import android.util.Log; +import android.util.Pair; import android.util.SparseArray; import android.util.TypedValue; import android.view.ContextThemeWrapper; @@ -307,6 +314,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { /** @see ViewRootImpl#mActivityConfigCallback */ private ActivityConfigCallback mActivityConfigCallback; + private OnContentApplyWindowInsetsListener mPendingOnContentApplyWindowInsetsListener; + static class WindowManagerHolder { static final IWindowManager sWindowManager = IWindowManager.Stub.asInterface( ServiceManager.getService("window")); @@ -2092,6 +2101,34 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { /** Notify when decor view is attached to window and {@link ViewRootImpl} is available. */ void onViewRootImplSet(ViewRootImpl viewRoot) { viewRoot.setActivityConfigCallback(mActivityConfigCallback); + if (mPendingOnContentApplyWindowInsetsListener != null) { + viewRoot.setOnContentApplyWindowInsetsListener( + mPendingOnContentApplyWindowInsetsListener); + mPendingOnContentApplyWindowInsetsListener = null; + } else { + viewRoot.setOnContentApplyWindowInsetsListener( + createDefaultContentWindowInsetsListener()); + } + } + + private OnContentApplyWindowInsetsListener createDefaultContentWindowInsetsListener() { + return insets -> { + if ((getDecorView().getWindowSystemUiVisibility() & SYSTEM_UI_LAYOUT_FLAGS) != 0) { + return new Pair<>(Insets.NONE, insets); + } + + boolean includeIme = (getAttributes().softInputMode & SOFT_INPUT_MASK_ADJUST) + == SOFT_INPUT_ADJUST_RESIZE; + Insets insetsToApply; + if (ViewRootImpl.sNewInsetsMode == 0) { + insetsToApply = insets.getSystemWindowInsets(); + } else { + insetsToApply = insets.getInsets(systemBars() | (includeIme ? ime() : 0)); + } + insets = insets.inset(insetsToApply); + return new Pair<>(insetsToApply, + insets.inset(insetsToApply).consumeSystemWindowInsets()); + }; } static private final String FOCUSED_ID_TAG = "android:focusedViewId"; @@ -2344,6 +2381,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { setFlags(0, flagsToUpdate); } else { setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate); + getAttributes().setFitWindowInsetsSides(0); + getAttributes().setFitWindowInsetsTypes(0); } if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) { @@ -2656,7 +2695,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { mContentParent = generateLayout(mDecor); // Set up decor part of UI to ignore fitsSystemWindows if appropriate. - mDecor.makeOptionalFitsSystemWindows(); + mDecor.makeFrameworkOptionalFitsSystemWindows(); final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById( R.id.decor_content_parent); @@ -3853,4 +3892,19 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { public List<Rect> getSystemGestureExclusionRects() { return getViewRootImpl().getRootSystemGestureExclusionRects(); } + + @Override + public void setOnContentApplyWindowInsetsListener(OnContentApplyWindowInsetsListener listener) { + ViewRootImpl impl = getViewRootImpl(); + if (impl != null) { + impl.setOnContentApplyWindowInsetsListener(listener); + } else { + mPendingOnContentApplyWindowInsetsListener = listener; + } + } + + @Override + public void resetOnContentApplyWindowInsetsListener() { + setOnContentApplyWindowInsetsListener(createDefaultContentWindowInsetsListener()); + } } diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java index 07d0d7d91997..f6089589a994 100644 --- a/core/java/com/android/internal/widget/MessagingLayout.java +++ b/core/java/com/android/internal/widget/MessagingLayout.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.annotation.StyleRes; import android.app.Notification; import android.app.Person; +import android.app.RemoteInputHistoryItem; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -161,8 +162,9 @@ public class MessagingLayout extends FrameLayout implements ImageMessageConsumer if (headerText != null) { mConversationTitle = headerText.getText(); } - addRemoteInputHistoryToMessages(newMessages, - extras.getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY)); + RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[]) + extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); + addRemoteInputHistoryToMessages(newMessages, history); boolean showSpinner = extras.getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false); bind(newMessages, newHistoricMessages, showSpinner); @@ -175,14 +177,18 @@ public class MessagingLayout extends FrameLayout implements ImageMessageConsumer private void addRemoteInputHistoryToMessages( List<Notification.MessagingStyle.Message> newMessages, - CharSequence[] remoteInputHistory) { + RemoteInputHistoryItem[] remoteInputHistory) { if (remoteInputHistory == null || remoteInputHistory.length == 0) { return; } for (int i = remoteInputHistory.length - 1; i >= 0; i--) { - CharSequence message = remoteInputHistory[i]; - newMessages.add(new Notification.MessagingStyle.Message( - message, 0, (Person) null, true /* remoteHistory */)); + RemoteInputHistoryItem historyMessage = remoteInputHistory[i]; + Notification.MessagingStyle.Message message = new Notification.MessagingStyle.Message( + historyMessage.getText(), 0, (Person) null, true /* remoteHistory */); + if (historyMessage.getUri() != null) { + message.setData(historyMessage.getMimeType(), historyMessage.getUri()); + } + newMessages.add(message); } } diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index 593728350037..32a7cf32bc0a 100755 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -243,7 +243,8 @@ Bitmap* GraphicsJNI::getNativeBitmap(JNIEnv* env, jobject bitmap) { return localBitmap.valid() ? &localBitmap->bitmap() : nullptr; } -SkImageInfo GraphicsJNI::getBitmapInfo(JNIEnv* env, jobject bitmap, uint32_t* outRowBytes) { +SkImageInfo GraphicsJNI::getBitmapInfo(JNIEnv* env, jobject bitmap, uint32_t* outRowBytes, + bool* isHardware) { SkASSERT(env); SkASSERT(bitmap); SkASSERT(env->IsInstanceOf(bitmap, gBitmap_class)); @@ -252,6 +253,9 @@ SkImageInfo GraphicsJNI::getBitmapInfo(JNIEnv* env, jobject bitmap, uint32_t* ou if (outRowBytes) { *outRowBytes = localBitmap->rowBytes(); } + if (isHardware) { + *isHardware = localBitmap->isHardware(); + } return localBitmap->info(); } diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h index 99034edaadf7..1e497654f18d 100644 --- a/core/jni/android/graphics/GraphicsJNI.h +++ b/core/jni/android/graphics/GraphicsJNI.h @@ -62,7 +62,8 @@ public: static android::Canvas* getNativeCanvas(JNIEnv*, jobject canvas); static android::Bitmap* getNativeBitmap(JNIEnv*, jobject bitmap); - static SkImageInfo getBitmapInfo(JNIEnv*, jobject bitmap, uint32_t* outRowBytes); + static SkImageInfo getBitmapInfo(JNIEnv*, jobject bitmap, uint32_t* outRowBytes, + bool* isHardware); static SkRegion* getNativeRegion(JNIEnv*, jobject region); /* diff --git a/core/jni/android/graphics/apex/android_bitmap.cpp b/core/jni/android/graphics/apex/android_bitmap.cpp index b8e04a7e9a2b..66285292743b 100644 --- a/core/jni/android/graphics/apex/android_bitmap.cpp +++ b/core/jni/android/graphics/apex/android_bitmap.cpp @@ -78,7 +78,7 @@ static SkColorType getColorType(AndroidBitmapFormat format) { } } -static uint32_t getInfoFlags(const SkImageInfo& info) { +static uint32_t getAlphaFlags(const SkImageInfo& info) { switch (info.alphaType()) { case kUnknown_SkAlphaType: LOG_ALWAYS_FATAL("Bitmap has no alpha type"); @@ -92,6 +92,14 @@ static uint32_t getInfoFlags(const SkImageInfo& info) { } } +static uint32_t getInfoFlags(const SkImageInfo& info, bool isHardware) { + uint32_t flags = getAlphaFlags(info); + if (isHardware) { + flags |= ANDROID_BITMAP_FLAGS_IS_HARDWARE; + } + return flags; +} + ABitmap* ABitmap_copy(ABitmap* srcBitmapHandle, AndroidBitmapFormat dstFormat) { SkColorType dstColorType = getColorType(dstFormat); if (srcBitmapHandle && dstColorType != kUnknown_SkColorType) { @@ -108,119 +116,32 @@ ABitmap* ABitmap_copy(ABitmap* srcBitmapHandle, AndroidBitmapFormat dstFormat) { return nullptr; } -static AndroidBitmapInfo getInfo(const SkImageInfo& imageInfo, uint32_t rowBytes) { +static AndroidBitmapInfo getInfo(const SkImageInfo& imageInfo, uint32_t rowBytes, bool isHardware) { AndroidBitmapInfo info; info.width = imageInfo.width(); info.height = imageInfo.height(); info.stride = rowBytes; info.format = getFormat(imageInfo); - info.flags = getInfoFlags(imageInfo); + info.flags = getInfoFlags(imageInfo, isHardware); return info; } AndroidBitmapInfo ABitmap_getInfo(ABitmap* bitmapHandle) { Bitmap* bitmap = TypeCast::toBitmap(bitmapHandle); - return getInfo(bitmap->info(), bitmap->rowBytes()); -} - -namespace { -static bool nearlyEqual(float a, float b) { - // By trial and error, this is close enough to match for the ADataSpaces we - // compare for. - return ::fabs(a - b) < .002f; -} - -static bool nearlyEqual(const skcms_TransferFunction& x, const skcms_TransferFunction& y) { - return nearlyEqual(x.g, y.g) - && nearlyEqual(x.a, y.a) - && nearlyEqual(x.b, y.b) - && nearlyEqual(x.c, y.c) - && nearlyEqual(x.d, y.d) - && nearlyEqual(x.e, y.e) - && nearlyEqual(x.f, y.f); + return getInfo(bitmap->info(), bitmap->rowBytes(), bitmap->isHardware()); } -static bool nearlyEqual(const skcms_Matrix3x3& x, const skcms_Matrix3x3& y) { - for (int i = 0; i < 3; i++) { - for (int j = 0; j < 3; j++) { - if (!nearlyEqual(x.vals[i][j], y.vals[i][j])) return false; - } - } - return true; -} - -static constexpr skcms_TransferFunction k2Dot6 = {2.6f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}; - -// Skia's SkNamedGamut::kDCIP3 is based on a white point of D65. This gamut -// matches the white point used by ColorSpace.Named.DCIP3. -static constexpr skcms_Matrix3x3 kDCIP3 = {{ - {0.486143, 0.323835, 0.154234}, - {0.226676, 0.710327, 0.0629966}, - {0.000800549, 0.0432385, 0.78275}, -}}; -} // anonymous namespace - ADataSpace ABitmap_getDataSpace(ABitmap* bitmapHandle) { Bitmap* bitmap = TypeCast::toBitmap(bitmapHandle); const SkImageInfo& info = bitmap->info(); - SkColorSpace* colorSpace = info.colorSpace(); - if (!colorSpace) { - return ADATASPACE_UNKNOWN; - } - - if (colorSpace->isSRGB()) { - if (info.colorType() == kRGBA_F16_SkColorType) { - return ADATASPACE_SCRGB; - } - return ADATASPACE_SRGB; - } - - skcms_TransferFunction fn; - LOG_ALWAYS_FATAL_IF(!colorSpace->isNumericalTransferFn(&fn)); - - skcms_Matrix3x3 gamut; - LOG_ALWAYS_FATAL_IF(!colorSpace->toXYZD50(&gamut)); - - if (nearlyEqual(gamut, SkNamedGamut::kSRGB)) { - if (nearlyEqual(fn, SkNamedTransferFn::kLinear)) { - // Skia doesn't differentiate amongst the RANGES. In Java, we associate - // LINEAR_EXTENDED_SRGB with F16, and LINEAR_SRGB with other Configs. - // Make the same association here. - if (info.colorType() == kRGBA_F16_SkColorType) { - return ADATASPACE_SCRGB_LINEAR; - } - return ADATASPACE_SRGB_LINEAR; - } - - if (nearlyEqual(fn, SkNamedTransferFn::kRec2020)) { - return ADATASPACE_BT709; - } - } - - if (nearlyEqual(fn, SkNamedTransferFn::kSRGB) && nearlyEqual(gamut, SkNamedGamut::kDCIP3)) { - return ADATASPACE_DISPLAY_P3; - } - - if (nearlyEqual(fn, SkNamedTransferFn::k2Dot2) && nearlyEqual(gamut, SkNamedGamut::kAdobeRGB)) { - return ADATASPACE_ADOBE_RGB; - } - - if (nearlyEqual(fn, SkNamedTransferFn::kRec2020) && - nearlyEqual(gamut, SkNamedGamut::kRec2020)) { - return ADATASPACE_BT2020; - } - - if (nearlyEqual(fn, k2Dot6) && nearlyEqual(gamut, kDCIP3)) { - return ADATASPACE_DCI_P3; - } - - return ADATASPACE_UNKNOWN; + return (ADataSpace)uirenderer::ColorSpaceToADataSpace(info.colorSpace(), info.colorType()); } AndroidBitmapInfo ABitmap_getInfoFromJava(JNIEnv* env, jobject bitmapObj) { uint32_t rowBytes = 0; - SkImageInfo imageInfo = GraphicsJNI::getBitmapInfo(env, bitmapObj, &rowBytes); - return getInfo(imageInfo, rowBytes); + bool isHardware = false; + SkImageInfo imageInfo = GraphicsJNI::getBitmapInfo(env, bitmapObj, &rowBytes, &isHardware); + return getInfo(imageInfo, rowBytes, isHardware); } void* ABitmap_getPixels(ABitmap* bitmapHandle) { @@ -263,7 +184,7 @@ SkAlphaType getAlphaType(const AndroidBitmapInfo* info) { class CompressWriter : public SkWStream { public: - CompressWriter(void* userContext, AndroidBitmap_compress_write_fn fn) + CompressWriter(void* userContext, AndroidBitmap_CompressWriteFunc fn) : mUserContext(userContext), mFn(fn), mBytesWritten(0) {} bool write(const void* buffer, size_t size) override { @@ -278,7 +199,7 @@ public: private: void* mUserContext; - AndroidBitmap_compress_write_fn mFn; + AndroidBitmap_CompressWriteFunc mFn; size_t mBytesWritten; }; @@ -286,7 +207,7 @@ private: int ABitmap_compress(const AndroidBitmapInfo* info, ADataSpace dataSpace, const void* pixels, AndroidBitmapCompressFormat inFormat, int32_t quality, void* userContext, - AndroidBitmap_compress_write_fn fn) { + AndroidBitmap_CompressWriteFunc fn) { Bitmap::JavaCompressFormat format; switch (inFormat) { case ANDROID_BITMAP_COMPRESS_FORMAT_JPEG: @@ -378,3 +299,12 @@ int ABitmap_compress(const AndroidBitmapInfo* info, ADataSpace dataSpace, const return ANDROID_BITMAP_RESULT_JNI_EXCEPTION; } } + +AHardwareBuffer* ABitmap_getHardwareBuffer(ABitmap* bitmapHandle) { + Bitmap* bitmap = TypeCast::toBitmap(bitmapHandle); + AHardwareBuffer* buffer = bitmap->hardwareBuffer(); + if (buffer) { + AHardwareBuffer_acquire(buffer); + } + return buffer; +} diff --git a/core/jni/android/graphics/apex/include/android/graphics/bitmap.h b/core/jni/android/graphics/apex/include/android/graphics/bitmap.h index 683851d09d93..45fec2ab7b43 100644 --- a/core/jni/android/graphics/apex/include/android/graphics/bitmap.h +++ b/core/jni/android/graphics/apex/include/android/graphics/bitmap.h @@ -21,6 +21,8 @@ #include <jni.h> #include <sys/cdefs.h> +struct AHardwareBuffer; + __BEGIN_DECLS /** @@ -61,7 +63,19 @@ jobject ABitmapConfig_getConfigFromFormat(JNIEnv* env, AndroidBitmapFormat forma // NDK access int ABitmap_compress(const AndroidBitmapInfo* info, ADataSpace dataSpace, const void* pixels, AndroidBitmapCompressFormat format, int32_t quality, void* userContext, - AndroidBitmap_compress_write_fn); + AndroidBitmap_CompressWriteFunc); +/** + * Retrieve the native object associated with a HARDWARE Bitmap. + * + * Client must not modify it while a Bitmap is wrapping it. + * + * @param bitmap Handle to an android.graphics.Bitmap. + * @return on success, a pointer to the + * AHardwareBuffer associated with bitmap. This acquires + * a reference on the buffer, and the client must call + * AHardwareBuffer_release when finished with it. + */ +AHardwareBuffer* ABitmap_getHardwareBuffer(ABitmap* bitmap); __END_DECLS @@ -116,6 +130,7 @@ namespace graphics { ADataSpace getDataSpace() const { return ABitmap_getDataSpace(mBitmap); } void* getPixels() const { return ABitmap_getPixels(mBitmap); } void notifyPixelsChanged() const { ABitmap_notifyPixelsChanged(mBitmap); } + AHardwareBuffer* getHardwareBuffer() const { return ABitmap_getHardwareBuffer(mBitmap); } private: // takes ownership of the provided ABitmap diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index e266fe623947..0156e23e94b6 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -307,6 +307,43 @@ static int _check_AudioSystem_Command(const char* caller, status_t status) return kAudioStatusError; } +static jint getVectorOfAudioDeviceTypeAddr(JNIEnv *env, jintArray deviceTypes, + jobjectArray deviceAddresses, + Vector<AudioDeviceTypeAddr> &audioDeviceTypeAddrVector) { + if (deviceTypes == nullptr || deviceAddresses == nullptr) { + return (jint)AUDIO_JAVA_BAD_VALUE; + } + jsize deviceCount = env->GetArrayLength(deviceTypes); + if (deviceCount == 0 || deviceCount != env->GetArrayLength(deviceAddresses)) { + return (jint)AUDIO_JAVA_BAD_VALUE; + } + // retrieve all device types + std::vector<audio_devices_t> deviceTypesVector; + jint *typesPtr = nullptr; + typesPtr = env->GetIntArrayElements(deviceTypes, 0); + if (typesPtr == nullptr) { + return (jint)AUDIO_JAVA_BAD_VALUE; + } + for (jint i = 0; i < deviceCount; i++) { + deviceTypesVector.push_back((audio_devices_t)typesPtr[i]); + } + // check each address is a string and add device type/address to list + jclass stringClass = FindClassOrDie(env, "java/lang/String"); + for (jint i = 0; i < deviceCount; i++) { + jobject addrJobj = env->GetObjectArrayElement(deviceAddresses, i); + if (!env->IsInstanceOf(addrJobj, stringClass)) { + return (jint)AUDIO_JAVA_BAD_VALUE; + } + const char *address = env->GetStringUTFChars((jstring)addrJobj, NULL); + AudioDeviceTypeAddr dev = AudioDeviceTypeAddr(typesPtr[i], address); + audioDeviceTypeAddrVector.add(dev); + env->ReleaseStringUTFChars((jstring)addrJobj, address); + } + env->ReleaseIntArrayElements(deviceTypes, typesPtr, 0); + + return (jint)NO_ERROR; +} + static jint android_media_AudioSystem_muteMicrophone(JNIEnv *env, jobject thiz, jboolean on) { @@ -1905,6 +1942,10 @@ static jint convertAudioMixToNative(JNIEnv *env, nCriterion.mValue.mUid = env->GetIntField(jCriterion, gAudioMixMatchCriterionFields.mIntProp); break; + case RULE_MATCH_USERID: + nCriterion.mValue.mUserId = + env->GetIntField(jCriterion, gAudioMixMatchCriterionFields.mIntProp); + break; case RULE_MATCH_ATTRIBUTE_USAGE: case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: { jobject jAttributes = env->GetObjectField(jCriterion, gAudioMixMatchCriterionFields.mAttr); @@ -1990,39 +2031,11 @@ exit: static jint android_media_AudioSystem_setUidDeviceAffinities(JNIEnv *env, jobject clazz, jint uid, jintArray deviceTypes, jobjectArray deviceAddresses) { - if (deviceTypes == nullptr || deviceAddresses == nullptr) { - return (jint) AUDIO_JAVA_BAD_VALUE; - } - jsize nb = env->GetArrayLength(deviceTypes); - if (nb == 0 || nb != env->GetArrayLength(deviceAddresses)) { - return (jint) AUDIO_JAVA_BAD_VALUE; - } - // retrieve all device types - std::vector<audio_devices_t> deviceTypesVector; - jint* typesPtr = nullptr; - typesPtr = env->GetIntArrayElements(deviceTypes, 0); - if (typesPtr == nullptr) { - return (jint) AUDIO_JAVA_BAD_VALUE; - } - for (jint i = 0; i < nb; i++) { - deviceTypesVector.push_back((audio_devices_t) typesPtr[i]); - } - - // check each address is a string and add device type/address to list for device affinity Vector<AudioDeviceTypeAddr> deviceVector; - jclass stringClass = FindClassOrDie(env, "java/lang/String"); - for (jint i = 0; i < nb; i++) { - jobject addrJobj = env->GetObjectArrayElement(deviceAddresses, i); - if (!env->IsInstanceOf(addrJobj, stringClass)) { - return (jint) AUDIO_JAVA_BAD_VALUE; - } - const char* address = env->GetStringUTFChars((jstring) addrJobj, NULL); - AudioDeviceTypeAddr dev = AudioDeviceTypeAddr(typesPtr[i], address); - deviceVector.add(dev); - env->ReleaseStringUTFChars((jstring) addrJobj, address); + jint results = getVectorOfAudioDeviceTypeAddr(env, deviceTypes, deviceAddresses, deviceVector); + if (results != NO_ERROR) { + return results; } - env->ReleaseIntArrayElements(deviceTypes, typesPtr, 0); - status_t status = AudioSystem::setUidDeviceAffinities((uid_t) uid, deviceVector); return (jint) nativeToJavaStatus(status); } @@ -2033,6 +2046,23 @@ static jint android_media_AudioSystem_removeUidDeviceAffinities(JNIEnv *env, job return (jint) nativeToJavaStatus(status); } +static jint android_media_AudioSystem_setUserIdDeviceAffinities(JNIEnv *env, jobject clazz, + jint userId, jintArray deviceTypes, + jobjectArray deviceAddresses) { + Vector<AudioDeviceTypeAddr> deviceVector; + jint results = getVectorOfAudioDeviceTypeAddr(env, deviceTypes, deviceAddresses, deviceVector); + if (results != NO_ERROR) { + return results; + } + status_t status = AudioSystem::setUserIdDeviceAffinities((int)userId, deviceVector); + return (jint)nativeToJavaStatus(status); +} + +static jint android_media_AudioSystem_removeUserIdDeviceAffinities(JNIEnv *env, jobject clazz, + jint userId) { + status_t status = AudioSystem::removeUserIdDeviceAffinities((int)userId); + return (jint)nativeToJavaStatus(status); +} static jint android_media_AudioSystem_systemReady(JNIEnv *env, jobject thiz) @@ -2463,7 +2493,9 @@ static const JNINativeMethod gMethods[] = { {"setPreferredDeviceForStrategy", "(IILjava/lang/String;)I", (void *)android_media_AudioSystem_setPreferredDeviceForStrategy}, {"removePreferredDeviceForStrategy", "(I)I", (void *)android_media_AudioSystem_removePreferredDeviceForStrategy}, {"getPreferredDeviceForStrategy", "(I[Landroid/media/AudioDeviceAddress;)I", (void *)android_media_AudioSystem_getPreferredDeviceForStrategy}, - {"getDevicesForAttributes", "(Landroid/media/AudioAttributes;[Landroid/media/AudioDeviceAddress;)I", (void *)android_media_AudioSystem_getDevicesForAttributes} + {"getDevicesForAttributes", "(Landroid/media/AudioAttributes;[Landroid/media/AudioDeviceAddress;)I", (void *)android_media_AudioSystem_getDevicesForAttributes}, + {"setUserIdDeviceAffinities", "(I[I[Ljava/lang/String;)I", (void *)android_media_AudioSystem_setUserIdDeviceAffinities}, + {"removeUserIdDeviceAffinities", "(I)I", (void *)android_media_AudioSystem_removeUserIdDeviceAffinities} }; static const JNINativeMethod gEventHandlerMethods[] = { diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp index 041019ec4841..a888b43a4854 100644 --- a/core/jni/android_media_AudioTrack.cpp +++ b/core/jni/android_media_AudioTrack.cpp @@ -1355,6 +1355,71 @@ static void android_media_AudioTrack_set_delay_padding(JNIEnv *env, jobject thi lpTrack->setParameters(param.toString()); } +static jint android_media_AudioTrack_setAudioDescriptionMixLeveldB(JNIEnv *env, jobject thiz, + jfloat level) { + sp<AudioTrack> lpTrack = getAudioTrack(env, thiz); + if (lpTrack == nullptr) { + jniThrowException(env, "java/lang/IllegalStateException", "AudioTrack not initialized"); + return (jint)AUDIO_JAVA_ERROR; + } + + // TODO: replace in r-dev or r-tv-dev with code if HW is able to set audio mix level. + return (jint)AUDIO_JAVA_ERROR; +} + +static jint android_media_AudioTrack_getAudioDescriptionMixLeveldB(JNIEnv *env, jobject thiz, + jfloatArray level) { + sp<AudioTrack> lpTrack = getAudioTrack(env, thiz); + if (lpTrack == nullptr) { + ALOGE("%s: AudioTrack not initialized", __func__); + return (jint)AUDIO_JAVA_ERROR; + } + jfloat *nativeLevel = (jfloat *)env->GetPrimitiveArrayCritical(level, NULL); + if (nativeLevel == nullptr) { + ALOGE("%s: Cannot retrieve level pointer", __func__); + return (jint)AUDIO_JAVA_ERROR; + } + + // TODO: replace in r-dev or r-tv-dev with code if HW is able to set audio mix level. + // By contract we can return -infinity if unsupported. + *nativeLevel = -std::numeric_limits<float>::infinity(); + env->ReleasePrimitiveArrayCritical(level, nativeLevel, 0 /* mode */); + nativeLevel = nullptr; + return (jint)AUDIO_JAVA_SUCCESS; +} + +static jint android_media_AudioTrack_setDualMonoMode(JNIEnv *env, jobject thiz, jint dualMonoMode) { + sp<AudioTrack> lpTrack = getAudioTrack(env, thiz); + if (lpTrack == nullptr) { + jniThrowException(env, "java/lang/IllegalStateException", "AudioTrack not initialized"); + return (jint)AUDIO_JAVA_ERROR; + } + + // TODO: replace in r-dev or r-tv-dev with code if HW is able to set audio mix level. + return (jint)AUDIO_JAVA_ERROR; +} + +static jint android_media_AudioTrack_getDualMonoMode(JNIEnv *env, jobject thiz, + jintArray dualMonoMode) { + sp<AudioTrack> lpTrack = getAudioTrack(env, thiz); + if (lpTrack == nullptr) { + ALOGE("%s: AudioTrack not initialized", __func__); + return (jint)AUDIO_JAVA_ERROR; + } + jfloat *nativeDualMonoMode = (jfloat *)env->GetPrimitiveArrayCritical(dualMonoMode, NULL); + if (nativeDualMonoMode == nullptr) { + ALOGE("%s: Cannot retrieve dualMonoMode pointer", __func__); + return (jint)AUDIO_JAVA_ERROR; + } + + // TODO: replace in r-dev or r-tv-dev with code if HW is able to select dual mono mode. + // By contract we can return DUAL_MONO_MODE_OFF if unsupported. + *nativeDualMonoMode = 0; // DUAL_MONO_MODE_OFF for now. + env->ReleasePrimitiveArrayCritical(dualMonoMode, nativeDualMonoMode, 0 /* mode */); + nativeDualMonoMode = nullptr; + return (jint)AUDIO_JAVA_SUCCESS; +} + // ---------------------------------------------------------------------------- // ---------------------------------------------------------------------------- static const JNINativeMethod gMethods[] = { @@ -1425,6 +1490,12 @@ static const JNINativeMethod gMethods[] = { {"native_setPresentation", "(II)I", (void *)android_media_AudioTrack_setPresentation}, {"native_getPortId", "()I", (void *)android_media_AudioTrack_get_port_id}, {"native_set_delay_padding", "(II)V", (void *)android_media_AudioTrack_set_delay_padding}, + {"native_set_audio_description_mix_level_db", "(F)I", + (void *)android_media_AudioTrack_setAudioDescriptionMixLeveldB}, + {"native_get_audio_description_mix_level_db", "([F)I", + (void *)android_media_AudioTrack_getAudioDescriptionMixLeveldB}, + {"native_set_dual_mono_mode", "(I)I", (void *)android_media_AudioTrack_setDualMonoMode}, + {"native_get_dual_mono_mode", "([I)I", (void *)android_media_AudioTrack_getDualMonoMode}, }; // field names found in android/media/AudioTrack.java diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp index 566c3850bf14..b52381113895 100644 --- a/core/jni/android_os_Debug.cpp +++ b/core/jni/android_os_Debug.cpp @@ -193,7 +193,8 @@ static int read_memtrack_memory(struct memtrack_proc* p, int pid, { int err = memtrack_proc_get(p, pid); if (err != 0) { - ALOGW("failed to get memory consumption info: %d", err); + // The memtrack HAL may not be available, do not log to avoid flooding + // logcat. return err; } diff --git a/core/jni/android_service_DataLoaderService.cpp b/core/jni/android_service_DataLoaderService.cpp index a62d127a968f..b307a73ed05b 100644 --- a/core/jni/android_service_DataLoaderService.cpp +++ b/core/jni/android_service_DataLoaderService.cpp @@ -35,24 +35,24 @@ static jboolean nativeCreateDataLoader(JNIEnv* env, static jboolean nativeStartDataLoader(JNIEnv* env, jobject thiz, jint storageId) { - return DataLoaderService_OnStart(storageId); + return DataLoaderService_OnStart(env, storageId); } static jboolean nativeStopDataLoader(JNIEnv* env, jobject thiz, jint storageId) { - return DataLoaderService_OnStop(storageId); + return DataLoaderService_OnStop(env, storageId); } static jboolean nativeDestroyDataLoader(JNIEnv* env, jobject thiz, jint storageId) { - return DataLoaderService_OnDestroy(storageId); + return DataLoaderService_OnDestroy(env, storageId); } static jboolean nativePrepareImage(JNIEnv* env, jobject thiz, jint storageId, jobject addedFiles, jobject removedFiles) { - return DataLoaderService_OnPrepareImage(storageId, addedFiles, removedFiles); + return DataLoaderService_OnPrepareImage(env, storageId, addedFiles, removedFiles); } static void nativeWriteData(JNIEnv* env, diff --git a/core/proto/android/app/job/enums.proto b/core/proto/android/app/job/enums.proto index f702b3e37bda..d2bf20591f92 100644 --- a/core/proto/android/app/job/enums.proto +++ b/core/proto/android/app/job/enums.proto @@ -34,4 +34,5 @@ enum StopReasonEnum { STOP_REASON_TIMEOUT = 3; STOP_REASON_DEVICE_IDLE = 4; STOP_REASON_DEVICE_THERMAL = 5; + STOP_REASON_RESTRAINED = 6; } diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto index b71e5395730e..303d62dbb30a 100644 --- a/core/proto/android/server/jobscheduler.proto +++ b/core/proto/android/server/jobscheduler.proto @@ -282,6 +282,10 @@ message ConstantsProto { // expected to run only {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS} within the past // WINDOW_SIZE_MS. optional int64 rare_window_size_ms = 6; + // The quota window size of the particular standby bucket. Apps in this standby bucket are + // expected to run only {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS} within the past + // WINDOW_SIZE_MS. + optional int64 restricted_window_size_ms = 20; // The maximum amount of time an app can have its jobs running within a 24 hour window. optional int64 max_execution_time_ms = 7; // The maximum number of jobs an app can run within this particular standby bucket's @@ -296,6 +300,9 @@ message ConstantsProto { // The maximum number of jobs an app can run within this particular standby bucket's // window size. optional int32 max_job_count_rare = 11; + // The maximum number of jobs an app can run within this particular standby bucket's + // window size. + optional int32 max_job_count_restricted = 21; // The period of time used to rate limit recently run jobs. optional int32 rate_limiting_window_ms = 19; // The maximum number of jobs that should be allowed to run in the past @@ -313,12 +320,17 @@ message ConstantsProto { // The maximum number of timing sessions an app can run within this particular standby // bucket's window size. optional int32 max_session_count_rare = 16; + // The maximum number of timing sessions an app can run within this particular standby + // bucket's window size. + optional int32 max_session_count_restricted = 22; // The maximum number of timing sessions that should be allowed to run in the past // rate_limiting_window_ms. optional int32 max_session_count_per_rate_limiting_window = 17; // Treat two distinct {@link TimingSession}s as the same if they start and end within this // amount of time of each other. optional int64 timing_session_coalescing_duration_ms = 18; + + // Next tag: 23 } optional QuotaController quota_controller = 24; @@ -943,6 +955,9 @@ message JobStatusDumpProto { optional JobInfo job_info = 6; repeated ConstraintEnum required_constraints = 7; + // Dynamic constraints are additional constraints imposed by the system that MUST be met before + // the app can run if the app does not have quota. + repeated ConstraintEnum dynamic_constraints = 31; repeated ConstraintEnum satisfied_constraints = 8; repeated ConstraintEnum unsatisfied_constraints = 9; optional bool is_doze_whitelisted = 10; @@ -956,6 +971,8 @@ message JobStatusDumpProto { // Battery Saver). This implicit constraint must be satisfied for the // job to run. optional bool is_not_restricted_in_bg = 2; + // True if dynamic constraints have been satisfied. + optional bool is_dynamic_satisfied = 3; } optional ImplicitConstraints implicit_constraints = 25; @@ -998,6 +1015,7 @@ message JobStatusDumpProto { FREQUENT = 2; RARE = 3; NEVER = 4; + RESTRICTED = 5; } optional Bucket standby_bucket = 17; optional bool is_exempted_from_app_standby = 27; @@ -1028,7 +1046,7 @@ message JobStatusDumpProto { // was no attempt. optional int64 time_since_first_force_batch_attempt_ms = 29; - // Next tag: 31 + // Next tag: 32 } // Dump from com.android.server.job.JobConcurrencyManager. diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 3c657530061b..bf610948d822 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1616,6 +1616,7 @@ <!-- Allows applications to request network recommendations and scores from the NetworkScoreService. + @SystemApi <p>Not for use by third-party applications. @hide --> <permission android:name="android.permission.REQUEST_NETWORK_SCORES" android:protectionLevel="signature|setup" /> @@ -2253,6 +2254,9 @@ <eat-comment /> <!-- @SystemApi @TestApi Allows an application to write to internal media storage + @deprecated This permission is no longer honored in the system and no longer adds + the media_rw gid as a supplementary gid to the holder. Use the + android.permission.MANAGE_EXTERNAL_STORAGE instead. @hide --> <permission android:name="android.permission.WRITE_MEDIA_STORAGE" android:protectionLevel="signature|privileged" /> @@ -2709,6 +2713,11 @@ <permission android:name="android.permission.READ_DEVICE_CONFIG" android:protectionLevel="signature|preinstalled" /> + <!-- @SystemApi @hide Allows an application to monitor config settings access. + <p>Not for use by third-party applications. --> + <permission android:name="android.permission.MONITOR_DEVICE_CONFIG_ACCESS" + android:protectionLevel="signature"/> + <!-- @SystemApi @TestApi Allows an application to call {@link android.app.ActivityManager#forceStopPackage}. @hide --> @@ -3793,6 +3802,24 @@ <permission android:name="android.permission.CAPTURE_MEDIA_OUTPUT" android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows an application to capture the audio played by other apps + with the {@code USAGE_VOICE_COMMUNICATION} usage. + + The application may opt out of capturing by setting an allow capture policy of + {@link android.media.AudioAttributes#ALLOW_CAPTURE_BY_NONE}. + + There are strong restriction listed at + {@link android.media.AudioAttributes#ALLOW_CAPTURE_BY_SYSTEM} + on what an app can do with the captured audio. + + See {@code CAPTURE_AUDIO_OUTPUT} and {@code CAPTURE_MEDIA_OUTPUT} for capturing + audio use cases other than voice communication playback. + + <p>Not for use by third-party applications.</p> + @hide --> + <permission android:name="android.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT" + android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows an application to capture audio for hotword detection. <p>Not for use by third-party applications.</p> @hide --> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 0895edc1da70..cfed805d5d88 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -2260,6 +2260,55 @@ <attr name="name" /> </declare-styleable> + <!-- The <code>processes</code> tag specifies the processes the application will run code in + and optionally characteristics of those processes. This tag is optional; if not + specified, components will simply run in the processes they specify. If supplied, + they can only specify processes that are enumerated here, and if they don't this + will be treated as a corrupt apk and result in an install failure. + + <p>This appears as a child tag of the + {@link #AndroidManifestApplication application} tag. --> + <declare-styleable name="AndroidManifestProcesses" parent="AndroidManifestApplication"> + </declare-styleable> + + <!-- The <code>process</code> tag enumerates one of the available processes under its + containing <code>processes</code> tag. + + <p>This appears as a child tag of the + {@link #AndroidManifestProcesses processes} tag. --> + <declare-styleable name="AndroidManifestProcess" parent="AndroidManifestProcesses"> + <!-- Required name of the process that is allowed --> + <attr name="process" /> + </declare-styleable> + + <!-- The <code>deny-permission</code> tag specifies that a permission is to be denied + for a particular process (if specified under the + {@link #AndroidManifestProcess process} tag) or by default for all + processes {if specified under the + @link #AndroidManifestProcesses processes} tag). + + <p>This appears as a child tag of the + {@link #AndroidManifestProcesses processes} and + {@link #AndroidManifestProcess process} tags. --> + <declare-styleable name="AndroidManifestDenyPermission" + parent="AndroidManifestProcesses"> + <!-- Required name of the permission that is to be denied --> + <attr name="name" /> + </declare-styleable> + + <!-- The <code>allow-permission</code> tag specifies that a permission is to be allowed + for a particular process, when it was previously denied for all processes through + {@link #AndroidManifestDenyPermission deny-permission} + + <p>This appears as a child tag of the + {@link #AndroidManifestProcesses processes} and + {@link #AndroidManifestProcess process} tags. --> + <declare-styleable name="AndroidManifestAllowPermission" + parent="AndroidManifestProcesses"> + <!-- Required name of the permission that is to be allowed. --> + <attr name="name" /> + </declare-styleable> + <!-- The <code>provider</code> tag declares a {@link android.content.ContentProvider} class that is available as part of the package's application components, supplying structured diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index fa70139def2d..42127e77317f 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1628,29 +1628,21 @@ config_timeZoneRulesUpdateTrackingEnabled are true.] --> <integer name="config_timeZoneRulesCheckRetryCount">5</integer> - <!-- Whether to enable network location overlay which allows network - location provider to be replaced by an app at run-time. When disabled, - only the config_networkLocationProviderPackageName package will be - searched for network location provider, otherwise packages whose - signature matches the signatures of config_locationProviderPackageNames - will be searched, and the service with the highest version number will - be picked. Anyone who wants to disable the overlay mechanism can set it - to false. - --> + <!-- Whether to enable network location overlay which allows network location provider to be + replaced by an app at run-time. When disabled, only the + config_networkLocationProviderPackageName package will be searched for network location + provider, otherwise any system package is eligible. Anyone who wants to disable the overlay + mechanism can set it to false. --> <bool name="config_enableNetworkLocationOverlay" translatable="false">true</bool> <!-- Package name providing network location support. Used only when config_enableNetworkLocationOverlay is false. --> <string name="config_networkLocationProviderPackageName" translatable="false">@null</string> - <!-- Whether to enable fused location provider overlay which allows fused - location provider to be replaced by an app at run-time. When disabled, - only the config_fusedLocationProviderPackageName package will be - searched for fused location provider, otherwise packages whose - signature matches the signatures of config_locationProviderPackageNames - will be searched, and the service with the highest version number will - be picked. Anyone who wants to disable the overlay mechanism can set it - to false. - --> + <!-- Whether to enable fused location provider overlay which allows fused location provider to + be replaced by an app at run-time. When disabled, only the + config_fusedLocationProviderPackageName package will be searched for fused location + provider, otherwise any system package is eligible. Anyone who wants to disable the overlay + mechanism can set it to false. --> <bool name="config_enableFusedLocationOverlay" translatable="false">true</bool> <!-- Package name providing fused location support. Used only when config_enableFusedLocationOverlay is false. --> @@ -1669,25 +1661,10 @@ --> <string name="config_defaultNetworkRecommendationProviderPackage" translatable="false"></string> - <!-- Whether to enable Hardware FLP overlay which allows Hardware FLP to be - replaced by an app at run-time. When disabled, only the - config_hardwareFlpPackageName package will be searched for Hardware Flp, - otherwise packages whose signature matches the signatures of - config_locationProviderPackageNames will be searched, and the service - with the highest version number will be picked. Anyone who wants to - disable the overlay mechanism can set it to false. - --> - <bool name="config_enableHardwareFlpOverlay" translatable="false">true</bool> - <!-- Package name providing Hardware Flp. Used only when - config_enableHardwareFlpOverlay is false. --> - <string name="config_hardwareFlpPackageName" translatable="false">com.android.location.fused</string> - <!-- Whether to enable geocoder overlay which allows geocoder to be replaced by an app at run-time. When disabled, only the config_geocoderProviderPackageName package will be searched for - geocoder, otherwise packages whose signature matches the signatures of - config_locationProviderPackageNames will be searched, and the service - with the highest version number will be picked. Anyone who wants to + geocoder, otherwise any system package is eligible. Anyone who wants to disable the overlay mechanism can set it to false. --> <bool name="config_enableGeocoderOverlay" translatable="false">true</bool> @@ -1698,9 +1675,7 @@ <!-- Whether to enable geofence overlay which allows geofence to be replaced by an app at run-time. When disabled, only the config_geofenceProviderPackageName package will be searched for - geofence implementation, otherwise packages whose signature matches the - signatures of config_locationProviderPackageNames will be searched, and - the service with the highest version number will be picked. Anyone who + geofence implementation, otherwise any system package is eligible. Anyone who wants to disable the overlay mechanism can set it to false. --> <bool name="config_enableGeofenceOverlay" translatable="false">true</bool> @@ -1711,9 +1686,7 @@ <!-- Whether to enable Hardware Activity-Recognition overlay which allows Hardware Activity-Recognition to be replaced by an app at run-time. When disabled, only the config_activityRecognitionHardwarePackageName package will be searched for - its implementation, otherwise packages whose signature matches the - signatures of config_locationProviderPackageNames will be searched, and - the service with the highest version number will be picked. Anyone who + its implementation, otherwise any system package is eligible. Anyone who wants to disable the overlay mechanism can set it to false. --> <bool name="config_enableActivityRecognitionHardwareOverlay" translatable="false">true</bool> @@ -1721,19 +1694,8 @@ config_enableActivityRecognitionHardwareOverlay is false. --> <string name="config_activityRecognitionHardwarePackageName" translatable="false">@null</string> - <!-- Package name(s) containing location provider support. - These packages can contain services implementing location providers, - such as the Geocode Provider, Network Location Provider, and - Fused Location Provider. They will each be searched for - service components implementing these providers. - It is strongly recommended that the packages explicitly named - below are on the system image, so that they will not map to - a 3rd party application. - The location framework also has support for installation - of new location providers at run-time. The new package does not - have to be explicitly listed here, however it must have a signature - that matches the signature of at least one package on this list. - --> + <!-- Package name(s) containing location provider support. These packages will be auto-granted + several permissions by the system, and should be system packages. --> <string-array name="config_locationProviderPackageNames" translatable="false"> <!-- The standard AOSP fused location provider --> <item>com.android.location.fused</item> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 1995a04f67ef..0cdd02cc9e0f 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -5321,4 +5321,8 @@ <!-- Accessibility description of caption view --> <string name="accessibility_freeform_caption">Caption bar of <xliff:g id="app_name">%1$s</xliff:g>.</string> + + <!-- Text to tell the user that a package has been forced by themselves in the RESTRICTED bucket. [CHAR LIMIT=NONE] --> + <string name="as_app_forced_to_restricted_bucket"> + <xliff:g id="package_name" example="com.android.example">%1$s</xliff:g> has been put into the RESTRICTED bucket</string> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 3aa3053fb7ed..8f2174562781 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1871,7 +1871,6 @@ <java-symbol type="bool" name="config_dozeAfterScreenOffByDefault" /> <java-symbol type="bool" name="config_enableActivityRecognitionHardwareOverlay" /> <java-symbol type="bool" name="config_enableFusedLocationOverlay" /> - <java-symbol type="bool" name="config_enableHardwareFlpOverlay" /> <java-symbol type="bool" name="config_enableGeocoderOverlay" /> <java-symbol type="bool" name="config_enableGeofenceOverlay" /> <java-symbol type="bool" name="config_enableNetworkLocationOverlay" /> @@ -2021,7 +2020,6 @@ <java-symbol type="string" name="config_datause_iface" /> <java-symbol type="string" name="config_activityRecognitionHardwarePackageName" /> <java-symbol type="string" name="config_fusedLocationProviderPackageName" /> - <java-symbol type="string" name="config_hardwareFlpPackageName" /> <java-symbol type="string" name="config_geocoderProviderPackageName" /> <java-symbol type="string" name="config_geofenceProviderPackageName" /> <java-symbol type="string" name="config_networkLocationProviderPackageName" /> @@ -3812,6 +3810,9 @@ <java-symbol type="string" name="config_rawContactsLocalAccountName" /> <java-symbol type="string" name="config_rawContactsLocalAccountType" /> + <!-- For App Standby --> + <java-symbol type="string" name="as_app_forced_to_restricted_bucket" /> + <!-- Assistant handles --> <java-symbol type="dimen" name="assist_handle_shadow_radius" /> diff --git a/core/tests/coretests/src/android/app/appsearch/SnippetTest.java b/core/tests/coretests/src/android/app/appsearch/SnippetTest.java new file mode 100644 index 000000000000..3103708f985d --- /dev/null +++ b/core/tests/coretests/src/android/app/appsearch/SnippetTest.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.appsearch; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.filters.SmallTest; + +import com.google.android.icing.proto.DocumentProto; +import com.google.android.icing.proto.PropertyProto; +import com.google.android.icing.proto.SearchResultProto; +import com.google.android.icing.proto.SnippetMatchProto; +import com.google.android.icing.proto.SnippetProto; + +import org.junit.Test; + +@SmallTest +public class SnippetTest { + + // TODO(sidchhabra): Add tests for Double and Long Snippets. + @Test + public void testSingleStringSnippet() { + + final String propertyKeyString = "content"; + final String propertyValueString = "A commonly used fake word is foo.\n" + + " Another nonsense word that’s used a lot\n" + + " is bar.\n"; + final String uri = "uri1"; + final String schemaType = "schema1"; + final String searchWord = "foo"; + final String exactMatch = "foo"; + final String window = "is foo"; + + // Building the SearchResult received from query. + PropertyProto property = PropertyProto.newBuilder() + .setName(propertyKeyString) + .addStringValues(propertyValueString) + .build(); + DocumentProto documentProto = DocumentProto.newBuilder() + .setUri(uri) + .setSchema(schemaType) + .addProperties(property) + .build(); + SnippetProto snippetProto = SnippetProto.newBuilder() + .addEntries(SnippetProto.EntryProto.newBuilder() + .setPropertyName(propertyKeyString) + .addSnippetMatches(SnippetMatchProto.newBuilder() + .setValuesIndex(0) + .setExactMatchPosition(29) + .setExactMatchBytes(3) + .setWindowPosition(26) + .setWindowBytes(6) + .build()) + .build()) + .build(); + SearchResultProto.ResultProto resultProto = SearchResultProto.ResultProto.newBuilder() + .setDocument(documentProto) + .setSnippet(snippetProto) + .build(); + SearchResultProto searchResultProto = SearchResultProto.newBuilder() + .addResults(resultProto) + .build(); + SearchResults searchResults = new SearchResults(searchResultProto); + + // Making ResultReader and getting Snippet values. + while (searchResults.hasNext()) { + SearchResults.Result result = searchResults.next(); + MatchInfo match = result.getMatchInfo().get(0); + assertThat(match.getPropertyPath()).isEqualTo(propertyKeyString); + assertThat(match.getFullText()).isEqualTo(propertyValueString); + assertThat(match.getExactMatch()).isEqualTo(exactMatch); + assertThat(match.getSnippet()).isEqualTo(window); + } + } + + // TODO(sidchhabra): Add tests for Double and Long Snippets. + @Test + public void testNoSnippets() { + + final String propertyKeyString = "content"; + final String propertyValueString = "A commonly used fake word is foo.\n" + + " Another nonsense word that’s used a lot\n" + + " is bar.\n"; + final String uri = "uri1"; + final String schemaType = "schema1"; + final String searchWord = "foo"; + final String exactMatch = "foo"; + final String window = "is foo"; + + // Building the SearchResult received from query. + PropertyProto property = PropertyProto.newBuilder() + .setName(propertyKeyString) + .addStringValues(propertyValueString) + .build(); + DocumentProto documentProto = DocumentProto.newBuilder() + .setUri(uri) + .setSchema(schemaType) + .addProperties(property) + .build(); + SearchResultProto.ResultProto resultProto = SearchResultProto.ResultProto.newBuilder() + .setDocument(documentProto) + .build(); + SearchResultProto searchResultProto = SearchResultProto.newBuilder() + .addResults(resultProto) + .build(); + SearchResults searchResults = new SearchResults(searchResultProto); + + while (searchResults.hasNext()) { + SearchResults.Result result = searchResults.next(); + assertThat(result.getMatchInfo()).isEqualTo(null); + } + } + + @Test + public void testMultipleStringSnippet() { + final String searchWord = "Test"; + + // Building the SearchResult received from query. + PropertyProto property1 = PropertyProto.newBuilder() + .setName("sender.name") + .addStringValues("Test Name Jr.") + .build(); + PropertyProto property2 = PropertyProto.newBuilder() + .setName("sender.email") + .addStringValues("TestNameJr@gmail.com") + .build(); + DocumentProto documentProto = DocumentProto.newBuilder() + .setUri("uri1") + .setSchema("schema1") + .addProperties(property1) + .addProperties(property2) + .build(); + SnippetProto snippetProto = SnippetProto.newBuilder() + .addEntries( + SnippetProto.EntryProto.newBuilder() + .setPropertyName("sender.name") + .addSnippetMatches( + SnippetMatchProto.newBuilder() + .setValuesIndex(0) + .setExactMatchPosition(0) + .setExactMatchBytes(4) + .setWindowPosition(0) + .setWindowBytes(9) + .build()) + .build()) + .addEntries( + SnippetProto.EntryProto.newBuilder() + .setPropertyName("sender.email") + .addSnippetMatches( + SnippetMatchProto.newBuilder() + .setValuesIndex(0) + .setExactMatchPosition(0) + .setExactMatchBytes(20) + .setWindowPosition(0) + .setWindowBytes(20) + .build()) + .build() + ) + .build(); + SearchResultProto.ResultProto resultProto = SearchResultProto.ResultProto.newBuilder() + .setDocument(documentProto) + .setSnippet(snippetProto) + .build(); + SearchResultProto searchResultProto = SearchResultProto.newBuilder() + .addResults(resultProto) + .build(); + SearchResults searchResults = new SearchResults(searchResultProto); + + // Making ResultReader and getting Snippet values. + while (searchResults.hasNext()) { + SearchResults.Result result = searchResults.next(); + + MatchInfo match1 = result.getMatchInfo().get(0); + assertThat(match1.getPropertyPath()).isEqualTo("sender.name"); + assertThat(match1.getFullText()).isEqualTo("Test Name Jr."); + assertThat(match1.getExactMatch()).isEqualTo("Test"); + assertThat(match1.getSnippet()).isEqualTo("Test Name"); + + MatchInfo match2 = result.getMatchInfo().get(1); + assertThat(match2.getPropertyPath()).isEqualTo("sender.email"); + assertThat(match2.getFullText()).isEqualTo("TestNameJr@gmail.com"); + assertThat(match2.getExactMatch()).isEqualTo("TestNameJr@gmail.com"); + assertThat(match2.getSnippet()).isEqualTo("TestNameJr@gmail.com"); + } + } +} diff --git a/core/tests/coretests/src/android/content/ApexContextTest.java b/core/tests/coretests/src/android/content/ApexContextTest.java new file mode 100644 index 000000000000..d15c64d0935d --- /dev/null +++ b/core/tests/coretests/src/android/content/ApexContextTest.java @@ -0,0 +1,47 @@ +/* + * 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.content; + +import static org.junit.Assert.assertEquals; + +import android.os.UserHandle; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ApexContextTest { + + @Test + public void dataDirectoryPathsAreAsExpected() { + ApexContext apexContext = ApexContext.getApexContext("my.apex"); + + assertEquals("/data/misc/apexdata/my.apex", + apexContext.getDeviceProtectedDataDir().getAbsolutePath()); + + assertEquals("/data/misc_de/5/apexdata/my.apex", + apexContext.getDeviceProtectedDataDirForUser(UserHandle.of(5)).getAbsolutePath()); + + assertEquals("/data/misc_ce/16/apexdata/my.apex", + apexContext.getCredentialProtectedDataDirForUser( + UserHandle.of(16)).getAbsolutePath()); + } +} diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java index f2852fa49b5e..bcf0b8ce4439 100644 --- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java +++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java @@ -41,6 +41,7 @@ import android.platform.test.annotations.Presubmit; import android.util.SparseArray; import android.view.SurfaceControl.Transaction; import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; +import android.view.animation.LinearInterpolator; import android.view.test.InsetsModeSession; import androidx.test.runner.AndroidJUnit4; @@ -121,7 +122,7 @@ public class InsetsAnimationControlImplTest { controls.put(ITYPE_NAVIGATION_BAR, navConsumer.getControl()); mController = new InsetsAnimationControlImpl(controls, new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(), - mMockController, 10 /* durationMs */, + mMockController, 10 /* durationMs */, new LinearInterpolator(), false /* fade */, LAYOUT_INSETS_DURING_ANIMATION_SHOWN); } diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index 838190387c35..f720c987a525 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -44,6 +44,7 @@ import android.platform.test.annotations.Presubmit; import android.view.WindowInsets.Type; import android.view.WindowManager.BadTokenException; import android.view.WindowManager.LayoutParams; +import android.view.animation.LinearInterpolator; import android.view.test.InsetsModeSession; import android.widget.TextView; @@ -148,7 +149,7 @@ public class InsetsControllerTest { WindowInsetsAnimationControlListener mockListener = mock(WindowInsetsAnimationControlListener.class); mController.controlWindowInsetsAnimation(statusBars(), 10 /* durationMs */, - mockListener); + new LinearInterpolator(), mockListener); // Ready gets deferred until next predraw mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw(); @@ -164,7 +165,8 @@ public class InsetsControllerTest { mController.getState().setDisplayFrame(new Rect(0, 0, 200, 200)); WindowInsetsAnimationControlListener controlListener = mock(WindowInsetsAnimationControlListener.class); - mController.controlWindowInsetsAnimation(0, 0 /* durationMs */, controlListener); + mController.controlWindowInsetsAnimation(0, 0 /* durationMs */, new LinearInterpolator(), + controlListener); verify(controlListener).onCancelled(); verify(controlListener, never()).onReady(any(), anyInt()); } @@ -375,7 +377,7 @@ public class InsetsControllerTest { WindowInsetsAnimationControlListener mockListener = mock(WindowInsetsAnimationControlListener.class); mController.controlWindowInsetsAnimation(statusBars(), 0 /* durationMs */, - mockListener); + new LinearInterpolator(), mockListener); ArgumentCaptor<WindowInsetsAnimationController> controllerCaptor = ArgumentCaptor.forClass(WindowInsetsAnimationController.class); diff --git a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java index 68099fe19d13..12c057f5a91a 100644 --- a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java +++ b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java @@ -18,9 +18,15 @@ package android.view.inputmethod; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import android.annotation.Nullable; import android.os.Parcel; import android.os.UserHandle; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.TextUtils; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -71,4 +77,227 @@ public class EditorInfoTest { } } } + + @Test + public void testNullTextInputComposeInitialSurroundingText() { + final Spannable testText = null; + final EditorInfo editorInfo = new EditorInfo(); + + try { + editorInfo.setInitialSurroundingText(testText); + fail("Shall not take null input"); + } catch (NullPointerException expected) { + // Expected behavior, nothing to do. + } + } + + @Test + public void testNonNullTextInputComposeInitialSurroundingText() { + final Spannable testText = createTestText(/* prependLength= */ 0, + EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH); + final EditorInfo editorInfo = new EditorInfo(); + + // Cursor at position 0. + int selectionLength = 0; + editorInfo.initialSelStart = 0; + editorInfo.initialSelEnd = editorInfo.initialSelStart + selectionLength; + int expectedTextBeforeCursorLength = 0; + int expectedTextAfterCursorLength = testText.length(); + + editorInfo.setInitialSurroundingText(testText); + + assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength, + expectedTextAfterCursorLength); + + // Cursor at the end. + editorInfo.initialSelStart = testText.length() - selectionLength; + editorInfo.initialSelEnd = testText.length(); + expectedTextBeforeCursorLength = testText.length(); + expectedTextAfterCursorLength = 0; + + editorInfo.setInitialSurroundingText(testText); + + assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength, + expectedTextAfterCursorLength); + + // Cursor at the middle. + selectionLength = 2; + editorInfo.initialSelStart = testText.length() / 2; + editorInfo.initialSelEnd = editorInfo.initialSelStart + selectionLength; + expectedTextBeforeCursorLength = editorInfo.initialSelStart; + expectedTextAfterCursorLength = testText.length() - editorInfo.initialSelEnd; + + editorInfo.setInitialSurroundingText(testText); + + assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength, + expectedTextAfterCursorLength); + + // Accidentally swap selection start and end. + editorInfo.initialSelEnd = testText.length() / 2; + editorInfo.initialSelStart = editorInfo.initialSelEnd + selectionLength; + + editorInfo.setInitialSurroundingText(testText); + + assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength, + expectedTextAfterCursorLength); + + // Invalid cursor position. + editorInfo.initialSelStart = -1; + + editorInfo.setInitialSurroundingText(testText); + + assertExpectedTextLength(editorInfo, + /* expectBeforeCursorLength= */null, + /* expectSelectionLength= */null, + /* expectAfterCursorLength= */null); + } + + @Test + public void testTooLongTextInputComposeInitialSurroundingText() { + final Spannable testText = createTestText(/* prependLength= */ 0, + EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH + 2); + final EditorInfo editorInfo = new EditorInfo(); + + // Cursor at position 0. + int selectionLength = 0; + editorInfo.initialSelStart = 0; + editorInfo.initialSelEnd = 0 + selectionLength; + int expectedTextBeforeCursorLength = 0; + int expectedTextAfterCursorLength = editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH; + + editorInfo.setInitialSurroundingText(testText); + + assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength, + expectedTextAfterCursorLength); + + // Cursor at the end. + editorInfo.initialSelStart = testText.length() - selectionLength; + editorInfo.initialSelEnd = testText.length(); + expectedTextBeforeCursorLength = editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH; + expectedTextAfterCursorLength = 0; + + editorInfo.setInitialSurroundingText(testText); + + assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength, + expectedTextAfterCursorLength); + + // Cursor at the middle. + selectionLength = 2; + editorInfo.initialSelStart = testText.length() / 2; + editorInfo.initialSelEnd = editorInfo.initialSelStart + selectionLength; + expectedTextBeforeCursorLength = Math.min(editorInfo.initialSelStart, + (int) (0.8 * (EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH - selectionLength))); + expectedTextAfterCursorLength = EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH + - expectedTextBeforeCursorLength - selectionLength; + + editorInfo.setInitialSurroundingText(testText); + + assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength, + expectedTextAfterCursorLength); + + // Accidentally swap selection start and end. + editorInfo.initialSelEnd = testText.length() / 2; + editorInfo.initialSelStart = editorInfo.initialSelEnd + selectionLength; + + editorInfo.setInitialSurroundingText(testText); + + assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength, + expectedTextAfterCursorLength); + + // Selection too long, selected text should be dropped. + selectionLength = EditorInfo.MAX_INITIAL_SELECTION_LENGTH + 1; + editorInfo.initialSelStart = testText.length() / 2; + editorInfo.initialSelEnd = editorInfo.initialSelStart + selectionLength; + expectedTextBeforeCursorLength = Math.min(editorInfo.initialSelStart, + (int) (0.8 * EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH)); + expectedTextAfterCursorLength = testText.length() - editorInfo.initialSelEnd; + + editorInfo.setInitialSurroundingText(testText); + + assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, + /* expectSelectionLength= */null, expectedTextAfterCursorLength); + } + + @Test + public void testTooLongSubTextInputComposeInitialSurroundingText() { + final int prependLength = 5; + final int subTextLength = EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH; + final Spannable fullText = createTestText(prependLength, subTextLength); + final EditorInfo editorInfo = new EditorInfo(); + // Cursor at the middle. + final int selectionLength = 2; + editorInfo.initialSelStart = fullText.length() / 2; + editorInfo.initialSelEnd = editorInfo.initialSelStart + selectionLength; + // #prependLength characters will be trimmed out. + final Spannable expectedTextBeforeCursor = createExpectedText(/* startNumber= */0, + editorInfo.initialSelStart - prependLength); + final Spannable expectedSelectedText = createExpectedText( + editorInfo.initialSelStart - prependLength, selectionLength); + final Spannable expectedTextAfterCursor = createExpectedText( + editorInfo.initialSelEnd - prependLength, + fullText.length() - editorInfo.initialSelEnd); + + editorInfo.setInitialSurroundingSubText(fullText.subSequence(prependLength, + fullText.length()), prependLength); + + assertTrue(TextUtils.equals(expectedTextBeforeCursor, + editorInfo.getInitialTextBeforeCursor(editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH, + InputConnection.GET_TEXT_WITH_STYLES))); + assertTrue(TextUtils.equals(expectedSelectedText, + editorInfo.getInitialSelectedText(InputConnection.GET_TEXT_WITH_STYLES))); + assertTrue(TextUtils.equals(expectedTextAfterCursor, + editorInfo.getInitialTextAfterCursor(editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH, + InputConnection.GET_TEXT_WITH_STYLES))); + } + + private static void assertExpectedTextLength(EditorInfo editorInfo, + @Nullable Integer expectBeforeCursorLength, @Nullable Integer expectSelectionLength, + @Nullable Integer expectAfterCursorLength) { + final CharSequence textBeforeCursor = + editorInfo.getInitialTextBeforeCursor(editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH, + InputConnection.GET_TEXT_WITH_STYLES); + final CharSequence selectedText = + editorInfo.getInitialSelectedText(InputConnection.GET_TEXT_WITH_STYLES); + final CharSequence textAfterCursor = + editorInfo.getInitialTextAfterCursor(editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH, + InputConnection.GET_TEXT_WITH_STYLES); + + if (expectBeforeCursorLength == null) { + assertNull(textBeforeCursor); + } else { + assertEquals(expectBeforeCursorLength.intValue(), textBeforeCursor.length()); + } + + if (expectSelectionLength == null) { + assertNull(selectedText); + } else { + assertEquals(expectSelectionLength.intValue(), selectedText.length()); + } + + if (expectAfterCursorLength == null) { + assertNull(textAfterCursor); + } else { + assertEquals(expectAfterCursorLength.intValue(), textAfterCursor.length()); + } + } + + private static Spannable createTestText(int prependLength, int surroundingLength) { + final SpannableStringBuilder builder = new SpannableStringBuilder(); + for (int i = 0; i < prependLength; i++) { + builder.append("a"); + } + + for (int i = 0; i < surroundingLength; i++) { + builder.append(Integer.toString(i % 10)); + } + return builder; + } + + private static Spannable createExpectedText(int startNumber, int length) { + final SpannableStringBuilder builder = new SpannableStringBuilder(); + for (int i = startNumber; i < startNumber + length; i++) { + builder.append(Integer.toString(i % 10)); + } + return builder; + } } diff --git a/data/etc/platform.xml b/data/etc/platform.xml index 877ef2687349..0541db121ae6 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -60,10 +60,6 @@ <group gid="log" /> </permission> - <permission name="android.permission.WRITE_MEDIA_STORAGE" > - <group gid="media_rw" /> - </permission> - <permission name="android.permission.ACCESS_MTP" > <group gid="mtp" /> </permission> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 18b5f5d6ac38..3677b8f9e66e 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -63,6 +63,7 @@ applications that come with the platform <privapp-permissions package="com.android.location.fused"> <permission name="android.permission.INSTALL_LOCATION_PROVIDER"/> + <permission name="android.permission.UPDATE_DEVICE_STATS"/> </privapp-permissions> <privapp-permissions package="com.android.managedprovisioning"> diff --git a/packages/WindowManager/OWNERS b/libs/WindowManager/OWNERS index 063d4594f2fa..063d4594f2fa 100644 --- a/packages/WindowManager/OWNERS +++ b/libs/WindowManager/OWNERS diff --git a/packages/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index b8934dc8c583..b8934dc8c583 100644 --- a/packages/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp diff --git a/packages/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml index ea8a5c305029..ea8a5c305029 100644 --- a/packages/WindowManager/Shell/AndroidManifest.xml +++ b/libs/WindowManager/Shell/AndroidManifest.xml diff --git a/packages/WindowManager/Shell/OWNERS b/libs/WindowManager/Shell/OWNERS index 4390004f5f93..4390004f5f93 100644 --- a/packages/WindowManager/Shell/OWNERS +++ b/libs/WindowManager/Shell/OWNERS diff --git a/packages/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml index c894eb0133b5..c894eb0133b5 100644 --- a/packages/WindowManager/Shell/res/values/config.xml +++ b/libs/WindowManager/Shell/res/values/config.xml diff --git a/packages/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShell.java b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShell.java index 273bd27a221b..273bd27a221b 100644 --- a/packages/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShell.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShell.java diff --git a/packages/WindowManager/Shell/tests/Android.bp b/libs/WindowManager/Shell/tests/Android.bp index 78fa45ebdf94..78fa45ebdf94 100644 --- a/packages/WindowManager/Shell/tests/Android.bp +++ b/libs/WindowManager/Shell/tests/Android.bp diff --git a/packages/WindowManager/Shell/tests/AndroidManifest.xml b/libs/WindowManager/Shell/tests/AndroidManifest.xml index a8f795ec8a8d..a8f795ec8a8d 100644 --- a/packages/WindowManager/Shell/tests/AndroidManifest.xml +++ b/libs/WindowManager/Shell/tests/AndroidManifest.xml diff --git a/packages/WindowManager/Shell/tests/AndroidTest.xml b/libs/WindowManager/Shell/tests/AndroidTest.xml index 4dce4db360e4..4dce4db360e4 100644 --- a/packages/WindowManager/Shell/tests/AndroidTest.xml +++ b/libs/WindowManager/Shell/tests/AndroidTest.xml diff --git a/packages/WindowManager/Shell/tests/res/values/config.xml b/libs/WindowManager/Shell/tests/res/values/config.xml index c894eb0133b5..c894eb0133b5 100644 --- a/packages/WindowManager/Shell/tests/res/values/config.xml +++ b/libs/WindowManager/Shell/tests/res/values/config.xml diff --git a/packages/WindowManager/Shell/tests/src/com/android/wm/shell/tests/WindowManagerShellTest.java b/libs/WindowManager/Shell/tests/src/com/android/wm/shell/tests/WindowManagerShellTest.java index 376875b143a1..376875b143a1 100644 --- a/packages/WindowManager/Shell/tests/src/com/android/wm/shell/tests/WindowManagerShellTest.java +++ b/libs/WindowManager/Shell/tests/src/com/android/wm/shell/tests/WindowManagerShellTest.java diff --git a/libs/hwui/DisplayListOps.in b/libs/hwui/DisplayListOps.in index 4a252afc1df3..49817925d9b4 100644 --- a/libs/hwui/DisplayListOps.in +++ b/libs/hwui/DisplayListOps.in @@ -14,39 +14,41 @@ * limitations under the License. */ -X(Flush) -X(Save) -X(Restore) +X(Flush) +X(Save) +X(Restore) X(SaveLayer) X(SaveBehind) -X(Concat) -X(SetMatrix) +X(Concat44) +X(Concat) +X(SetMatrix) +X(Scale) X(Translate) -X(ClipPath) -X(ClipRect) -X(ClipRRect) +X(ClipPath) +X(ClipRect) +X(ClipRRect) X(ClipRegion) X(DrawPaint) X(DrawBehind) -X(DrawPath) -X(DrawRect) -X(DrawRegion) -X(DrawOval) +X(DrawPath) +X(DrawRect) +X(DrawRegion) +X(DrawOval) X(DrawArc) -X(DrawRRect) -X(DrawDRRect) -X(DrawAnnotation) -X(DrawDrawable) +X(DrawRRect) +X(DrawDRRect) +X(DrawAnnotation) +X(DrawDrawable) X(DrawPicture) -X(DrawImage) -X(DrawImageNine) -X(DrawImageRect) +X(DrawImage) +X(DrawImageNine) +X(DrawImageRect) X(DrawImageLattice) X(DrawTextBlob) -X(DrawPatch) -X(DrawPoints) -X(DrawVertices) -X(DrawAtlas) +X(DrawPatch) +X(DrawPoints) +X(DrawVertices) +X(DrawAtlas) X(DrawShadowRec) X(DrawVectorDrawable) X(DrawWebView) diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp index c0df2faf120a..dc467c41baed 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -130,6 +130,12 @@ struct SaveBehind final : Op { } }; +struct Concat44 final : Op { + static const auto kType = Type::Concat44; + Concat44(const SkScalar m[16]) { memcpy(colMajor, m, sizeof(colMajor)); } + SkScalar colMajor[16]; + void draw(SkCanvas* c, const SkMatrix&) const { c->experimental_concat44(colMajor); } +}; struct Concat final : Op { static const auto kType = Type::Concat; Concat(const SkMatrix& matrix) : matrix(matrix) {} @@ -144,6 +150,12 @@ struct SetMatrix final : Op { c->setMatrix(SkMatrix::Concat(original, matrix)); } }; +struct Scale final : Op { + static const auto kType = Type::Scale; + Scale(SkScalar sx, SkScalar sy) : sx(sx), sy(sy) {} + SkScalar sx, sy; + void draw(SkCanvas* c, const SkMatrix&) const { c->scale(sx, sy); } +}; struct Translate final : Op { static const auto kType = Type::Translate; Translate(SkScalar dx, SkScalar dy) : dx(dx), dy(dy) {} @@ -562,12 +574,18 @@ void DisplayListData::saveBehind(const SkRect* subset) { this->push<SaveBehind>(0, subset); } +void DisplayListData::concat44(const SkScalar colMajor[16]) { + this->push<Concat44>(0, colMajor); +} void DisplayListData::concat(const SkMatrix& matrix) { this->push<Concat>(0, matrix); } void DisplayListData::setMatrix(const SkMatrix& matrix) { this->push<SetMatrix>(0, matrix); } +void DisplayListData::scale(SkScalar sx, SkScalar sy) { + this->push<Scale>(0, sx, sy); +} void DisplayListData::translate(SkScalar dx, SkScalar dy) { this->push<Translate>(0, dx, dy); } @@ -823,12 +841,18 @@ bool RecordingCanvas::onDoSaveBehind(const SkRect* subset) { return false; } +void RecordingCanvas::didConcat44(const SkScalar colMajor[16]) { + fDL->concat44(colMajor); +} void RecordingCanvas::didConcat(const SkMatrix& matrix) { fDL->concat(matrix); } void RecordingCanvas::didSetMatrix(const SkMatrix& matrix) { fDL->setMatrix(matrix); } +void RecordingCanvas::didScale(SkScalar sx, SkScalar sy) { + fDL->scale(sx, sy); +} void RecordingCanvas::didTranslate(SkScalar dx, SkScalar dy) { fDL->translate(dx, dy); } diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h index 322eff24dd34..7eb1ce3eb18a 100644 --- a/libs/hwui/RecordingCanvas.h +++ b/libs/hwui/RecordingCanvas.h @@ -82,8 +82,10 @@ private: void saveBehind(const SkRect*); void restore(); + void concat44(const SkScalar colMajor[16]); void concat(const SkMatrix&); void setMatrix(const SkMatrix&); + void scale(SkScalar, SkScalar); void translate(SkScalar, SkScalar); void translateZ(SkScalar); @@ -153,8 +155,10 @@ public: void onFlush() override; + void didConcat44(const SkScalar[16]) override; void didConcat(const SkMatrix&) override; void didSetMatrix(const SkMatrix&) override; + void didScale(SkScalar, SkScalar) override; void didTranslate(SkScalar, SkScalar) override; void onClipRect(const SkRect&, SkClipOp, ClipEdgeStyle) override; diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp index a6c4e9db7280..4b2857f6c290 100644 --- a/libs/hwui/hwui/ImageDecoder.cpp +++ b/libs/hwui/hwui/ImageDecoder.cpp @@ -31,7 +31,7 @@ ImageDecoder::ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, sk_sp<SkPngChu , mDecodeSize(mTargetSize) , mOutColorType(mCodec->computeOutputColorType(kN32_SkColorType)) , mUnpremultipliedRequired(false) - , mOutColorSpace(mCodec->getInfo().refColorSpace()) + , mOutColorSpace(mCodec->computeOutputColorSpace(mOutColorType, nullptr)) , mSampleSize(1) { } @@ -111,7 +111,6 @@ bool ImageDecoder::setOutColorType(SkColorType colorType) { if (!gray()) { return false; } - mOutColorSpace = nullptr; break; case kN32_SkColorType: break; @@ -137,9 +136,15 @@ void ImageDecoder::setOutColorSpace(sk_sp<SkColorSpace> colorSpace) { mOutColorSpace = std::move(colorSpace); } +sk_sp<SkColorSpace> ImageDecoder::getOutputColorSpace() const { + // kGray_8 is used for ALPHA_8, which ignores the color space. + return mOutColorType == kGray_8_SkColorType ? nullptr : mOutColorSpace; +} + + SkImageInfo ImageDecoder::getOutputInfo() const { SkISize size = mCropRect ? mCropRect->size() : mTargetSize; - return SkImageInfo::Make(size, mOutColorType, getOutAlphaType(), mOutColorSpace); + return SkImageInfo::Make(size, mOutColorType, getOutAlphaType(), getOutputColorSpace()); } bool ImageDecoder::opaque() const { @@ -154,7 +159,7 @@ SkCodec::Result ImageDecoder::decode(void* pixels, size_t rowBytes) { void* decodePixels = pixels; size_t decodeRowBytes = rowBytes; auto decodeInfo = SkImageInfo::Make(mDecodeSize, mOutColorType, getOutAlphaType(), - mOutColorSpace); + getOutputColorSpace()); // Used if we need a temporary before scaling or subsetting. // FIXME: Use scanline decoding on only a couple lines to save memory. b/70709380. SkBitmap tmp; diff --git a/libs/hwui/hwui/ImageDecoder.h b/libs/hwui/hwui/ImageDecoder.h index 96f97e5421f3..0c99f84cbb72 100644 --- a/libs/hwui/hwui/ImageDecoder.h +++ b/libs/hwui/hwui/ImageDecoder.h @@ -66,6 +66,7 @@ private: ImageDecoder& operator=(const ImageDecoder&) = delete; SkAlphaType getOutAlphaType() const; + sk_sp<SkColorSpace> getOutputColorSpace() const; }; } // namespace android diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp index a1b2b18195bc..aa8849b642b1 100644 --- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp +++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp @@ -26,6 +26,7 @@ #include "SkAndroidFrameworkUtils.h" #include "SkClipStack.h" #include "SkRect.h" +#include "include/private/SkM44.h" namespace android { namespace uirenderer { @@ -92,7 +93,7 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { SkIRect surfaceBounds = canvas->internal_private_getTopLayerBounds(); SkIRect clipBounds = canvas->getDeviceClipBounds(); - SkMatrix44 mat4(canvas->getTotalMatrix()); + SkM44 mat4(canvas->experimental_getLocalToDevice()); SkRegion clipRegion; canvas->temporary_internal_getRgnClip(&clipRegion); @@ -118,7 +119,7 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { // update the matrix and clip that we pass to the WebView to match the coordinates of // the offscreen layer - mat4.preTranslate(-clipBounds.fLeft, -clipBounds.fTop, 0); + mat4.preTranslate(-clipBounds.fLeft, -clipBounds.fTop); clipBounds.offsetTo(0, 0); clipRegion.translate(-surfaceBounds.fLeft, -surfaceBounds.fTop); @@ -126,7 +127,7 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { // we are drawing into a (clipped) offscreen layer so we must update the clip and matrix // from device coordinates to the layer's coordinates clipBounds.offset(-surfaceBounds.fLeft, -surfaceBounds.fTop); - mat4.preTranslate(-surfaceBounds.fLeft, -surfaceBounds.fTop, 0); + mat4.preTranslate(-surfaceBounds.fLeft, -surfaceBounds.fTop); } DrawGlInfo info; @@ -137,7 +138,7 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { info.isLayer = fboID != 0; info.width = fboSize.width(); info.height = fboSize.height(); - mat4.asColMajorf(&info.transform[0]); + mat4.getColMajor(&info.transform[0]); info.color_space_ptr = canvas->imageInfo().colorSpace(); // ensure that the framebuffer that the webview will render into is bound before we clear diff --git a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp index 112792611fc3..68f111752a4c 100644 --- a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp +++ b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp @@ -20,6 +20,7 @@ #include <GrBackendDrawableInfo.h> #include <SkAndroidFrameworkUtils.h> #include <SkImage.h> +#include "include/private/SkM44.h" #include <utils/Color.h> #include <utils/Trace.h> #include <utils/TraceUtils.h> @@ -62,7 +63,7 @@ void VkFunctorDrawHandler::draw(const GrBackendDrawableInfo& info) { renderthread::RenderThread::getInstance().vulkanManager(); mFunctorHandle->initVk(vk_manager.getVkFunctorInitParams()); - SkMatrix44 mat4(mMatrix); + SkM44 mat4(mMatrix); VkFunctorDrawParams params{ .width = mImageInfo.width(), .height = mImageInfo.height(), @@ -72,7 +73,7 @@ void VkFunctorDrawHandler::draw(const GrBackendDrawableInfo& info) { .clip_right = mClip.fRight, .clip_bottom = mClip.fBottom, }; - mat4.asColMajorf(¶ms.transform[0]); + mat4.getColMajor(¶ms.transform[0]); params.secondary_command_buffer = vulkan_info.fSecondaryCommandBuffer; params.color_attachment_index = vulkan_info.fColorAttachmentIndex; params.compatible_render_pass = vulkan_info.fCompatibleRenderPass; diff --git a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp index 706325f00bd2..241d3708def5 100644 --- a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp +++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp @@ -121,7 +121,7 @@ void VkInteropFunctorDrawable::onDraw(SkCanvas* canvas) { glBindTexture(GL_TEXTURE_2D, 0); DrawGlInfo info; - SkMatrix44 mat4(canvas->getTotalMatrix()); + SkM44 mat4(canvas->experimental_getLocalToDevice()); SkIRect clipBounds = canvas->getDeviceClipBounds(); info.clipLeft = clipBounds.fLeft; @@ -131,7 +131,7 @@ void VkInteropFunctorDrawable::onDraw(SkCanvas* canvas) { info.isLayer = true; info.width = mFBInfo.width(); info.height = mFBInfo.height(); - mat4.asColMajorf(&info.transform[0]); + mat4.getColMajor(&info.transform[0]); info.color_space_ptr = canvas->imageInfo().colorSpace(); glViewport(0, 0, info.width, info.height); diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp index c445885c5c63..71a27ced2e09 100644 --- a/libs/hwui/utils/Color.cpp +++ b/libs/hwui/utils/Color.cpp @@ -108,7 +108,9 @@ SkColorType PixelFormatToColorType(android::PixelFormat format) { } } -// FIXME: Share with the version in android_bitmap.cpp? +namespace { +static constexpr skcms_TransferFunction k2Dot6 = {2.6f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}; + // Skia's SkNamedGamut::kDCIP3 is based on a white point of D65. This gamut // matches the white point used by ColorSpace.Named.DCIP3. static constexpr skcms_Matrix3x3 kDCIP3 = {{ @@ -117,6 +119,87 @@ static constexpr skcms_Matrix3x3 kDCIP3 = {{ {0.000800549, 0.0432385, 0.78275}, }}; +static bool nearlyEqual(float a, float b) { + // By trial and error, this is close enough to match for the ADataSpaces we + // compare for. + return ::fabs(a - b) < .002f; +} + +static bool nearlyEqual(const skcms_TransferFunction& x, const skcms_TransferFunction& y) { + return nearlyEqual(x.g, y.g) + && nearlyEqual(x.a, y.a) + && nearlyEqual(x.b, y.b) + && nearlyEqual(x.c, y.c) + && nearlyEqual(x.d, y.d) + && nearlyEqual(x.e, y.e) + && nearlyEqual(x.f, y.f); +} + +static bool nearlyEqual(const skcms_Matrix3x3& x, const skcms_Matrix3x3& y) { + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + if (!nearlyEqual(x.vals[i][j], y.vals[i][j])) return false; + } + } + return true; +} + +} // anonymous namespace + +android_dataspace ColorSpaceToADataSpace(SkColorSpace* colorSpace, SkColorType colorType) { + if (!colorSpace) { + return HAL_DATASPACE_UNKNOWN; + } + + if (colorSpace->isSRGB()) { + if (colorType == kRGBA_F16_SkColorType) { + return HAL_DATASPACE_V0_SCRGB; + } + return HAL_DATASPACE_V0_SRGB; + } + + skcms_TransferFunction fn; + LOG_ALWAYS_FATAL_IF(!colorSpace->isNumericalTransferFn(&fn)); + + skcms_Matrix3x3 gamut; + LOG_ALWAYS_FATAL_IF(!colorSpace->toXYZD50(&gamut)); + + if (nearlyEqual(gamut, SkNamedGamut::kSRGB)) { + if (nearlyEqual(fn, SkNamedTransferFn::kLinear)) { + // Skia doesn't differentiate amongst the RANGES. In Java, we associate + // LINEAR_EXTENDED_SRGB with F16, and LINEAR_SRGB with other Configs. + // Make the same association here. + if (colorType == kRGBA_F16_SkColorType) { + return HAL_DATASPACE_V0_SCRGB_LINEAR; + } + return HAL_DATASPACE_V0_SRGB_LINEAR; + } + + if (nearlyEqual(fn, SkNamedTransferFn::kRec2020)) { + return HAL_DATASPACE_V0_BT709; + } + } + + if (nearlyEqual(fn, SkNamedTransferFn::kSRGB) && nearlyEqual(gamut, SkNamedGamut::kDCIP3)) { + return HAL_DATASPACE_DISPLAY_P3; + } + + if (nearlyEqual(fn, SkNamedTransferFn::k2Dot2) && nearlyEqual(gamut, SkNamedGamut::kAdobeRGB)) { + return HAL_DATASPACE_ADOBE_RGB; + } + + if (nearlyEqual(fn, SkNamedTransferFn::kRec2020) && + nearlyEqual(gamut, SkNamedGamut::kRec2020)) { + return HAL_DATASPACE_BT2020; + } + + if (nearlyEqual(fn, k2Dot6) && nearlyEqual(gamut, kDCIP3)) { + return HAL_DATASPACE_DCI_P3; + } + + return HAL_DATASPACE_UNKNOWN; +} + sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace) { if (dataspace == HAL_DATASPACE_UNKNOWN) { return SkColorSpace::MakeSRGB(); @@ -126,7 +209,7 @@ sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace) { // needs to use the locally-defined kDCIP3 gamut, rather than the one in // Skia (SkNamedGamut), which is used for other data spaces with // HAL_DATASPACE_STANDARD_DCI_P3 (e.g. HAL_DATASPACE_DISPLAY_P3). - return SkColorSpace::MakeRGB({2.6f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, kDCIP3); + return SkColorSpace::MakeRGB(k2Dot6, kDCIP3); } skcms_Matrix3x3 gamut; @@ -165,7 +248,7 @@ sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace) { case HAL_DATASPACE_TRANSFER_GAMMA2_2: return SkColorSpace::MakeRGB({2.2f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, gamut); case HAL_DATASPACE_TRANSFER_GAMMA2_6: - return SkColorSpace::MakeRGB({2.6f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, gamut); + return SkColorSpace::MakeRGB(k2Dot6, gamut); case HAL_DATASPACE_TRANSFER_GAMMA2_8: return SkColorSpace::MakeRGB({2.8f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, gamut); case HAL_DATASPACE_TRANSFER_ST2084: diff --git a/libs/hwui/utils/Color.h b/libs/hwui/utils/Color.h index 07b5ec8fe0f0..a76f7e499c37 100644 --- a/libs/hwui/utils/Color.h +++ b/libs/hwui/utils/Color.h @@ -105,6 +105,22 @@ ANDROID_API SkColorType PixelFormatToColorType(android::PixelFormat format); ANDROID_API sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace); +/** + * Return the android_dataspace corresponding to colorSpace. + * + * Note: This currently only returns android_dataspaces with corresponding + * ADataSpaces. The NDK relies on this, so if you need to update it to return + * an android_dataspace *without* an ADataSpace, the NDK methods need to be + * updated. + * + * @param colorSpace May be null, in which case this will return + * HAL_DATASPACE_UNKNOWN. + * @param colorType Some SkColorSpaces are associated with more than one + * android_dataspace. In that case, the SkColorType is used to + * determine which one to return. + */ +ANDROID_API android_dataspace ColorSpaceToADataSpace(SkColorSpace*, SkColorType); + struct Lab { float L; float a; diff --git a/location/java/android/location/GnssMeasurement.java b/location/java/android/location/GnssMeasurement.java index 70abbb3019fc..3eeb3a2051e9 100644 --- a/location/java/android/location/GnssMeasurement.java +++ b/location/java/android/location/GnssMeasurement.java @@ -778,10 +778,12 @@ public final class GnssMeasurement implements Parcelable { /** * Gets the Carrier-to-noise density in dB-Hz. * - * <p>Typical range: 10-50 db-Hz. + * <p>Typical range: 10-50 dB-Hz. The range of possible C/N0 values is 0-63 dB-Hz to handle + * some edge cases. * * <p>The value contains the measured C/N0 for the signal at the antenna input. */ + @FloatRange(from = 0, to = 63) public double getCn0DbHz() { return mCn0DbHz; } @@ -805,13 +807,14 @@ public final class GnssMeasurement implements Parcelable { /** * Gets the baseband carrier-to-noise density in dB-Hz. * - * <p>Typical range: 0-50 dB-Hz. + * <p>Typical range: 10-50 dB-Hz. The range of possible baseband C/N0 values is 0-63 dB-Hz to + * handle some edge cases. * * <p>The value contains the measured C/N0 for the signal at the baseband. This is typically * a few dB weaker than the value estimated for C/N0 at the antenna port, which is reported * in {@link #getCn0DbHz()}. */ - @FloatRange(from = 0, to = 50) + @FloatRange(from = 0, to = 63) public double getBasebandCn0DbHz() { return mBasebandCn0DbHz; } diff --git a/location/java/android/location/GnssNavigationMessage.java b/location/java/android/location/GnssNavigationMessage.java index a83db3fcf384..ca0bfb1added 100644 --- a/location/java/android/location/GnssNavigationMessage.java +++ b/location/java/android/location/GnssNavigationMessage.java @@ -16,9 +16,9 @@ package android.location; -import android.annotation.TestApi; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; @@ -39,7 +39,8 @@ public final class GnssNavigationMessage implements Parcelable { */ @Retention(RetentionPolicy.SOURCE) @IntDef({TYPE_UNKNOWN, TYPE_GPS_L1CA, TYPE_GPS_L2CNAV, TYPE_GPS_L5CNAV, TYPE_GPS_CNAV2, - TYPE_GLO_L1CA, TYPE_BDS_D1, TYPE_BDS_D2, TYPE_GAL_I, TYPE_GAL_F}) + TYPE_SBS, TYPE_GLO_L1CA, TYPE_QZS_L1CA, TYPE_BDS_D1, TYPE_BDS_D2, TYPE_BDS_CNAV1, + TYPE_BDS_CNAV2, TYPE_GAL_I, TYPE_GAL_F, TYPE_IRN_L5CA}) public @interface GnssNavigationMessageType {} // The following enumerations must be in sync with the values declared in gps.h @@ -54,16 +55,26 @@ public final class GnssNavigationMessage implements Parcelable { public static final int TYPE_GPS_L5CNAV = 0x0103; /** GPS CNAV-2 message contained in the structure. */ public static final int TYPE_GPS_CNAV2 = 0x0104; + /** SBAS message contained in the structure. */ + public static final int TYPE_SBS = 0x0201; /** Glonass L1 CA message contained in the structure. */ public static final int TYPE_GLO_L1CA = 0x0301; + /** QZSS L1 C/A message contained in the structure. */ + public static final int TYPE_QZS_L1CA = 0x0401; /** Beidou D1 message contained in the structure. */ public static final int TYPE_BDS_D1 = 0x0501; /** Beidou D2 message contained in the structure. */ public static final int TYPE_BDS_D2 = 0x0502; + /** Beidou CNAV1 message contained in the structure. */ + public static final int TYPE_BDS_CNAV1 = 0x0503; + /** Beidou CNAV2 message contained in the structure. */ + public static final int TYPE_BDS_CNAV2 = 0x0504; /** Galileo I/NAV message contained in the structure. */ public static final int TYPE_GAL_I = 0x0601; /** Galileo F/NAV message contained in the structure. */ public static final int TYPE_GAL_F = 0x0602; + /** IRNSS L5 C/A message contained in the structure. */ + public static final int TYPE_IRN_L5CA = 0x0701; /** * The Navigation Message Status is 'unknown'. @@ -199,16 +210,26 @@ public final class GnssNavigationMessage implements Parcelable { return "GPS L5-CNAV"; case TYPE_GPS_CNAV2: return "GPS CNAV2"; + case TYPE_SBS: + return "SBS"; case TYPE_GLO_L1CA: return "Glonass L1 C/A"; + case TYPE_QZS_L1CA: + return "QZSS L1 C/A"; case TYPE_BDS_D1: return "Beidou D1"; case TYPE_BDS_D2: return "Beidou D2"; + case TYPE_BDS_CNAV1: + return "Beidou CNAV1"; + case TYPE_BDS_CNAV2: + return "Beidou CNAV2"; case TYPE_GAL_I: return "Galileo I"; case TYPE_GAL_F: return "Galileo F"; + case TYPE_IRN_L5CA: + return "IRNSS L5 C/A"; default: return "<Invalid:" + mType + ">"; } diff --git a/location/java/com/android/internal/location/ProviderRequest.java b/location/java/com/android/internal/location/ProviderRequest.java index 8d8df4533ebe..572fbc373730 100644 --- a/location/java/com/android/internal/location/ProviderRequest.java +++ b/location/java/com/android/internal/location/ProviderRequest.java @@ -23,11 +23,10 @@ import android.os.Parcelable; import android.os.WorkSource; import android.util.TimeUtils; -import com.android.internal.util.Preconditions; - import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; /** @hide */ public final class ProviderRequest implements Parcelable { @@ -76,8 +75,8 @@ public final class ProviderRequest implements Parcelable { this.interval = interval; this.lowPowerMode = lowPowerMode; this.locationSettingsIgnored = locationSettingsIgnored; - this.locationRequests = Preconditions.checkNotNull(locationRequests); - this.workSource = Preconditions.checkNotNull(workSource); + this.locationRequests = Objects.requireNonNull(locationRequests); + this.workSource = Objects.requireNonNull(workSource); } public static final Parcelable.Creator<ProviderRequest> CREATOR = @@ -119,6 +118,7 @@ public final class ProviderRequest implements Parcelable { for (LocationRequest request : locationRequests) { request.writeToParcel(parcel, flags); } + parcel.writeParcelable(workSource, flags); } @Override @@ -155,40 +155,50 @@ public final class ProviderRequest implements Parcelable { return mInterval; } - public void setInterval(long interval) { + /** Sets the request interval. */ + public Builder setInterval(long interval) { this.mInterval = interval; + return this; } public boolean isLowPowerMode() { return mLowPowerMode; } - public void setLowPowerMode(boolean lowPowerMode) { + /** Sets whether low power mode is enabled. */ + public Builder setLowPowerMode(boolean lowPowerMode) { this.mLowPowerMode = lowPowerMode; + return this; } public boolean isLocationSettingsIgnored() { return mLocationSettingsIgnored; } - public void setLocationSettingsIgnored(boolean locationSettingsIgnored) { + /** Sets whether location settings should be ignored. */ + public Builder setLocationSettingsIgnored(boolean locationSettingsIgnored) { this.mLocationSettingsIgnored = locationSettingsIgnored; + return this; } public List<LocationRequest> getLocationRequests() { return mLocationRequests; } - public void setLocationRequests(List<LocationRequest> locationRequests) { - this.mLocationRequests = Preconditions.checkNotNull(locationRequests); + /** Sets the {@link LocationRequest}s associated with this request. */ + public Builder setLocationRequests(List<LocationRequest> locationRequests) { + this.mLocationRequests = Objects.requireNonNull(locationRequests); + return this; } public WorkSource getWorkSource() { return mWorkSource; } - public void setWorkSource(WorkSource workSource) { - mWorkSource = Preconditions.checkNotNull(workSource); + /** Sets the work source. */ + public Builder setWorkSource(WorkSource workSource) { + mWorkSource = Objects.requireNonNull(workSource); + return this; } /** diff --git a/location/lib/java/com/android/location/provider/ProviderRequestUnbundled.java b/location/lib/java/com/android/location/provider/ProviderRequestUnbundled.java index d12d6b777856..b650efc91907 100644 --- a/location/lib/java/com/android/location/provider/ProviderRequestUnbundled.java +++ b/location/lib/java/com/android/location/provider/ProviderRequestUnbundled.java @@ -34,6 +34,7 @@ import java.util.List; * of this package for more information. */ public final class ProviderRequestUnbundled { + private final ProviderRequest mRequest; /** @hide */ diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java index cb132f5b1101..6e63d1704fcb 100644 --- a/media/java/android/media/AudioDeviceInfo.java +++ b/media/java/android/media/AudioDeviceInfo.java @@ -422,6 +422,40 @@ public final class AudioDeviceInfo { return AudioFormat.filterPublicFormats(mPort.formats()); } + /** + * Returns an array of supported encapsulation modes for the device. + * + * The array can include any of + * {@link AudioTrack#ENCAPSULATION_MODE_ELEMENTARY_STREAM}, + * {@link AudioTrack#ENCAPSULATION_MODE_HANDLE}. + * + * @return An array of supported encapsulation modes for the device. This + * may be an empty array if no encapsulation modes are supported. + */ + public @NonNull int[] getEncapsulationModes() { + // Implement a getter in r-dev or r-tv-dev as needed. + return new int[0]; // be careful of returning a copy of any internal data. + } + + /** + * Returns an array of supported encapsulation metadata types for the device. + * + * The metadata type returned should be allowed for all encapsulation modes supported + * by the device. Some metadata types may apply only to certain + * compressed stream formats, the returned list is the union of subsets. + * + * The array can include any of + * {@link AudioTrack#ENCAPSULATION_METADATA_TYPE_FRAMEWORK_TUNER}, + * {@link AudioTrack#ENCAPSULATION_METADATA_TYPE_DVB_AD_DESCRIPTOR}. + * + * @return An array of supported encapsulation metadata types for the device. This + * may be an empty array if no metadata types are supported. + */ + public @NonNull int[] getEncapsulationMetadataTypes() { + // Implement a getter in r-dev or r-tv-dev as needed. + return new int[0]; // be careful of returning a copy of any internal data. + } + /** * @return The device type identifier of the audio device (i.e. TYPE_BUILTIN_SPEAKER). */ diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 8ad5c04a1f85..861b76d2b153 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -4567,6 +4567,70 @@ public class AudioManager { } /** + * @hide + * Sets an additional audio output device delay in milliseconds. + * + * The additional output delay is a request to the output device to + * delay audio presentation (generally with respect to video presentation for better + * synchronization). + * It may not be supported by all output devices, + * and typically increases the audio latency by the amount of additional + * audio delay requested. + * + * If additional audio delay is supported by an audio output device, + * it is expected to be supported for all output streams (and configurations) + * opened on that device. + * + * @param device an instance of {@link AudioDeviceInfo} returned from {@link getDevices()}. + * @param delayMs delay in milliseconds desired. This should be in range of {@code 0} + * to the value returned by {@link #getMaxAdditionalOutputDeviceDelay()}. + * @return true if successful, false if the device does not support output device delay + * or the delay is not in range of {@link #getMaxAdditionalOutputDeviceDelay()}. + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public boolean setAdditionalOutputDeviceDelay( + @NonNull AudioDeviceInfo device, @IntRange(from = 0) int delayMs) { + Objects.requireNonNull(device); + // Implement the setter in r-dev or r-tv-dev as needed. + return false; + } + + /** + * @hide + * Returns the current additional audio output device delay in milliseconds. + * + * @param device an instance of {@link AudioDeviceInfo} returned from {@link getDevices()}. + * @return the additional output device delay. This is a non-negative number. + * {@code 0} is returned if unsupported. + */ + @SystemApi + @IntRange(from = 0) + public int getAdditionalOutputDeviceDelay(@NonNull AudioDeviceInfo device) { + Objects.requireNonNull(device); + // Implement the getter in r-dev or r-tv-dev as needed. + return 0; + } + + /** + * @hide + * Returns the maximum additional audio output device delay in milliseconds. + * + * @param device an instance of {@link AudioDeviceInfo} returned from {@link getDevices()}. + * @return the maximum output device delay in milliseconds that can be set. + * This is a non-negative number + * representing the additional audio delay supported for the device. + * {@code 0} is returned if unsupported. + */ + @SystemApi + @IntRange(from = 0) + public int getMaxAdditionalOutputDeviceDelay(@NonNull AudioDeviceInfo device) { + Objects.requireNonNull(device); + // Implement the getter in r-dev or r-tv-dev as needed. + return 0; + } + + /** * Returns the estimated latency for the given stream type in milliseconds. * * DO NOT UNHIDE. The existing approach for doing A/V sync has too many problems. We need diff --git a/media/java/android/media/AudioPlaybackCaptureConfiguration.java b/media/java/android/media/AudioPlaybackCaptureConfiguration.java index 453704eea398..65f2f1789491 100644 --- a/media/java/android/media/AudioPlaybackCaptureConfiguration.java +++ b/media/java/android/media/AudioPlaybackCaptureConfiguration.java @@ -102,6 +102,12 @@ public final class AudioPlaybackCaptureConfiguration { criterion -> criterion.getIntProp()); } + /** @return the userId's passed to {@link Builder#addMatchingUserId(int)}. */ + public @NonNull int[] getMatchingUserIds() { + return getIntPredicates(AudioMixingRule.RULE_MATCH_USERID, + criterion -> criterion.getIntProp()); + } + /** @return the usages passed to {@link Builder#excludeUsage(int)}. */ @AttributeUsage public @NonNull int[] getExcludeUsages() { @@ -115,6 +121,12 @@ public final class AudioPlaybackCaptureConfiguration { criterion -> criterion.getIntProp()); } + /** @return the userId's passed to {@link Builder#excludeUserId(int)}. */ + public @NonNull int[] getExcludeUserIds() { + return getIntPredicates(AudioMixingRule.RULE_EXCLUDE_USERID, + criterion -> criterion.getIntProp()); + } + private int[] getIntPredicates(int rule, ToIntFunction<AudioMixMatchCriterion> getPredicate) { return mAudioMixingRule.getCriteria().stream() @@ -153,6 +165,7 @@ public final class AudioPlaybackCaptureConfiguration { private final MediaProjection mProjection; private int mUsageMatchType = MATCH_TYPE_UNSPECIFIED; private int mUidMatchType = MATCH_TYPE_UNSPECIFIED; + private int mUserIdMatchType = MATCH_TYPE_UNSPECIFIED; /** @param projection A MediaProjection that supports audio projection. */ public Builder(@NonNull MediaProjection projection) { @@ -202,6 +215,23 @@ public final class AudioPlaybackCaptureConfiguration { } /** + * Only capture audio output by app with the matching {@code userId}. + * + * <p>If called multiple times, will capture audio output by apps whose userId is any of the + * given userId's. + * + * @throws IllegalStateException if called in conjunction with {@link #excludeUserId(int)}. + */ + public @NonNull Builder addMatchingUserId(int userId) { + Preconditions.checkState( + mUserIdMatchType != MATCH_TYPE_EXCLUSIVE, + ERROR_MESSAGE_MISMATCHED_RULES); + mAudioMixingRuleBuilder.addMixRule(AudioMixingRule.RULE_MATCH_USERID, userId); + mUserIdMatchType = MATCH_TYPE_INCLUSIVE; + return this; + } + + /** * Only capture audio output that does not match the given {@link AudioAttributes}. * * <p>If called multiple times, will capture audio output that does not match any of the @@ -238,6 +268,24 @@ public final class AudioPlaybackCaptureConfiguration { } /** + * Only capture audio output by apps that do not have the matching {@code userId}. + * + * <p>If called multiple times, will capture audio output by apps whose userId is not any of + * the given userId's. + * + * @throws IllegalStateException if called in conjunction with + * {@link #addMatchingUserId(int)}. + */ + public @NonNull Builder excludeUserId(int userId) { + Preconditions.checkState( + mUserIdMatchType != MATCH_TYPE_INCLUSIVE, + ERROR_MESSAGE_MISMATCHED_RULES); + mAudioMixingRuleBuilder.excludeMixRule(AudioMixingRule.RULE_MATCH_USERID, userId); + mUserIdMatchType = MATCH_TYPE_EXCLUSIVE; + return this; + } + + /** * Builds the configuration instance. * * @throws UnsupportedOperationException if the parameters set are incompatible. diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 48d27faa0855..02cb8aafea0c 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -1169,6 +1169,13 @@ public class AudioSystem /** see AudioPolicy.removeUidDeviceAffinities() */ public static native int removeUidDeviceAffinities(int uid); + /** see AudioPolicy.setUserIdDeviceAffinities() */ + public static native int setUserIdDeviceAffinities(int userId, @NonNull int[] types, + @NonNull String[] addresses); + + /** see AudioPolicy.removeUserIdDeviceAffinities() */ + public static native int removeUserIdDeviceAffinities(int userId); + public static native int systemReady(); public static native float getStreamVolumeDB(int stream, int index, int device); diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index f566f646f1e0..81275f6891ab 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -256,6 +256,103 @@ public class AudioTrack extends PlayerBase */ public static final int ENCAPSULATION_MODE_HANDLE = 2; + /* Enumeration of metadata types permitted for use by + * encapsulation mode audio streams. + */ + /** @hide */ + @IntDef(prefix = { "ENCAPSULATION_METADATA_TYPE_" }, value = { + ENCAPSULATION_METADATA_TYPE_NONE, /* reserved */ + ENCAPSULATION_METADATA_TYPE_FRAMEWORK_TUNER, + ENCAPSULATION_METADATA_TYPE_DVB_AD_DESCRIPTOR, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface EncapsulationMetadataType {} + + /** + * Reserved do not use. + * @hide + */ + public static final int ENCAPSULATION_METADATA_TYPE_NONE = 0; // reserved + + /** + * Encapsulation metadata type for framework tuner information. + * + * TODO(b/147778408) Link: Fill in Tuner API info. + */ + public static final int ENCAPSULATION_METADATA_TYPE_FRAMEWORK_TUNER = 1; + + /** + * Encapsulation metadata type for DVB AD descriptor. + * + * This metadata is formatted per ETSI TS 101 154 Table E.1: AD_descriptor. + */ + public static final int ENCAPSULATION_METADATA_TYPE_DVB_AD_DESCRIPTOR = 2; + + /* Dual Mono handling is used when a stereo audio stream + * contains separate audio content on the left and right channels. + * Such information about the content of the stream may be found, for example, in + * ITU T-REC-J.94-201610 A.6.2.3 Component descriptor. + */ + /** @hide */ + @IntDef({ + DUAL_MONO_MODE_OFF, + DUAL_MONO_MODE_LR, + DUAL_MONO_MODE_LL, + DUAL_MONO_MODE_RR, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DualMonoMode {} + // Important: The DUAL_MONO_MODE values must be kept in sync with native header files. + /** + * This mode disables any Dual Mono presentation effect. + * + */ + public static final int DUAL_MONO_MODE_OFF = 0; + + /** + * This mode indicates that a stereo stream should be presented + * with the left and right audio channels blended together + * and delivered to both channels. + * + * Behavior for non-stereo streams is implementation defined. + * A suggested guideline is that the left-right stereo symmetric + * channels are pairwise blended; + * the other channels such as center are left alone. + * + * The Dual Mono effect occurs before volume scaling. + */ + public static final int DUAL_MONO_MODE_LR = 1; + + /** + * This mode indicates that a stereo stream should be presented + * with the left audio channel replicated into the right audio channel. + * + * Behavior for non-stereo streams is implementation defined. + * A suggested guideline is that all channels with left-right + * stereo symmetry will have the left channel position replicated + * into the right channel position. + * The center channels (with no left/right symmetry) or unbalanced + * channels are left alone. + * + * The Dual Mono effect occurs before volume scaling. + */ + public static final int DUAL_MONO_MODE_LL = 2; + + /** + * This mode indicates that a stereo stream should be presented + * with the right audio channel replicated into the left audio channel. + * + * Behavior for non-stereo streams is implementation defined. + * A suggested guideline is that all channels with left-right + * stereo symmetry will have the right channel position replicated + * into the left channel position. + * The center channels (with no left/right symmetry) or unbalanced + * channels are left alone. + * + * The Dual Mono effect occurs before volume scaling. + */ + public static final int DUAL_MONO_MODE_RR = 3; + /** @hide */ @IntDef({ WRITE_BLOCKING, @@ -1355,6 +1452,140 @@ public class AudioTrack extends PlayerBase attributes.getContentType(), attributes.getUsage(), attributes.getFlags()); } + /* + * The MAX_LEVEL should be exactly representable by an IEEE 754-2008 base32 float. + * This means fractions must be divisible by a power of 2. For example, + * 10.25f is OK as 0.25 is 1/4, but 10.1f is NOT OK as 1/10 is not expressable by + * a finite binary fraction. + * + * 48.f is the nominal max for API level {@link android os.Build.VERSION_CODES#R}. + * We use this to suggest a baseline range for implementation. + * + * The API contract specification allows increasing this value in a future + * API release, but not decreasing this value. + */ + private static final float MAX_AUDIO_DESCRIPTION_MIX_LEVEL = 48.f; + + private static boolean isValidAudioDescriptionMixLevel(float level) { + return !(Float.isNaN(level) || level > MAX_AUDIO_DESCRIPTION_MIX_LEVEL); + } + + /** + * Sets the Audio Description mix level in dB. + * + * For AudioTracks incorporating a secondary Audio Description stream + * (where such contents may be sent through an Encapsulation Mode + * {@link #ENCAPSULATION_MODE_ELEMENTARY_STREAM} or {@link #ENCAPSULATION_MODE_HANDLE} + * or internally by a HW channel), + * the level of mixing of the Audio Description to the Main Audio stream + * is controlled by this method. + * + * Such mixing occurs <strong>prior</strong> to overall volume scaling. + * + * @param level a floating point value between + * {@code Float.NEGATIVE_INFINITY} to {@code +48.f}, + * where {@code Float.NEGATIVE_INFINITY} means the Audio Description is not mixed + * and a level of {@code 0.f} means the Audio Description is mixed without scaling. + * @return true on success, false on failure. + */ + public boolean setAudioDescriptionMixLeveldB( + @FloatRange(to = 48.f, toInclusive = true) float level) { + if (!isValidAudioDescriptionMixLevel(level)) { + throw new IllegalArgumentException("level is out of range" + level); + } + return native_set_audio_description_mix_level_db(level) == SUCCESS; + } + + /** + * Returns the Audio Description mix level in dB. + * + * If Audio Description mixing is unavailable from the hardware device, + * a value of {@code Float.NEGATIVE_INFINITY} is returned. + * + * @return the current Audio Description Mix Level in dB. + * A value of {@code Float.NEGATIVE_INFINITY} means + * that the audio description is not mixed or + * the hardware is not available. + * This should reflect the <strong>true</strong> internal device mix level; + * hence the application might receive any floating value + * except {@code Float.NaN}. + */ + public float getAudioDescriptionMixLeveldB() { + float[] level = { Float.NEGATIVE_INFINITY }; + try { + final int status = native_get_audio_description_mix_level_db(level); + if (status != SUCCESS || Float.isNaN(level[0])) { + return Float.NEGATIVE_INFINITY; + } + } catch (Exception e) { + return Float.NEGATIVE_INFINITY; + } + return level[0]; + } + + private static boolean isValidDualMonoMode(@DualMonoMode int dualMonoMode) { + switch (dualMonoMode) { + case DUAL_MONO_MODE_OFF: + case DUAL_MONO_MODE_LR: + case DUAL_MONO_MODE_LL: + case DUAL_MONO_MODE_RR: + return true; + default: + return false; + } + } + + /** + * Sets the Dual Mono mode presentation on the output device. + * + * The Dual Mono mode is generally applied to stereo audio streams + * where the left and right channels come from separate sources. + * + * For compressed audio, where the decoding is done in hardware, + * Dual Mono presentation needs to be performed + * by the hardware output device + * as the PCM audio is not available to the framework. + * + * @param dualMonoMode one of {@link #DUAL_MONO_MODE_OFF}, + * {@link #DUAL_MONO_MODE_LR}, + * {@link #DUAL_MONO_MODE_LL}, + * {@link #DUAL_MONO_MODE_RR}. + * + * @return true on success, false on failure if the output device + * does not support Dual Mono mode. + */ + public boolean setDualMonoMode(@DualMonoMode int dualMonoMode) { + if (!isValidDualMonoMode(dualMonoMode)) { + throw new IllegalArgumentException( + "Invalid Dual Mono mode " + dualMonoMode); + } + return native_set_dual_mono_mode(dualMonoMode) == SUCCESS; + } + + /** + * Returns the Dual Mono mode presentation setting. + * + * If no Dual Mono presentation is available for the output device, + * then {@link #DUAL_MONO_MODE_OFF} is returned. + * + * @return one of {@link #DUAL_MONO_MODE_OFF}, + * {@link #DUAL_MONO_MODE_LR}, + * {@link #DUAL_MONO_MODE_LL}, + * {@link #DUAL_MONO_MODE_RR}. + */ + public @DualMonoMode int getDualMonoMode() { + int[] dualMonoMode = { DUAL_MONO_MODE_OFF }; + try { + final int status = native_get_dual_mono_mode(dualMonoMode); + if (status != SUCCESS || !isValidDualMonoMode(dualMonoMode[0])) { + return DUAL_MONO_MODE_OFF; + } + } catch (Exception e) { + return DUAL_MONO_MODE_OFF; + } + return dualMonoMode[0]; + } + // mask of all the positional channels supported, however the allowed combinations // are further restricted by the matching left/right rule and // AudioSystem.OUT_CHANNEL_COUNT_MAX @@ -3947,6 +4178,11 @@ public class AudioTrack extends PlayerBase private native void native_set_delay_padding(int delayInFrames, int paddingInFrames); + private native int native_set_audio_description_mix_level_db(float level); + private native int native_get_audio_description_mix_level_db(float[] level); + private native int native_set_dual_mono_mode(int dualMonoMode); + private native int native_get_dual_mono_mode(int[] dualMonoMode); + //--------------------------------------------------------- // Utility methods //------------------ diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 7f1c69218f45..1f97be5c3f4d 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -266,6 +266,10 @@ interface IAudioService { int removeUidDeviceAffinity(in IAudioPolicyCallback pcb, in int uid); + int setUserIdDeviceAffinity(in IAudioPolicyCallback pcb, in int userId, in int[] deviceTypes, + in String[] deviceAddresses); + int removeUserIdDeviceAffinity(in IAudioPolicyCallback pcb, in int userId); + boolean hasHapticChannels(in Uri uri); boolean isCallScreeningModeSupported(); diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index f780d40d349b..abc7e0b7be0e 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -23,6 +23,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.graphics.ImageFormat; import android.graphics.Rect; import android.graphics.SurfaceTexture; +import android.hardware.HardwareBuffer; import android.media.MediaCodecInfo.CodecCapabilities; import android.os.Build; import android.os.Bundle; @@ -39,9 +40,13 @@ import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.ReadOnlyBufferException; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -1736,7 +1741,25 @@ final public class MediaCodec { { int index = msg.arg2; synchronized(mBufferLock) { - validateInputByteBuffer(mCachedInputBuffers, index); + switch (mBufferMode) { + case BUFFER_MODE_LEGACY: + validateInputByteBuffer(mCachedInputBuffers, index); + break; + case BUFFER_MODE_BLOCK: + while (mQueueRequests.size() <= index) { + mQueueRequests.add(null); + } + QueueRequest request = mQueueRequests.get(index); + if (request == null) { + request = new QueueRequest(mCodec, index); + mQueueRequests.set(index, request); + } + request.setAccessible(true); + break; + default: + throw new IllegalStateException( + "Unrecognized buffer mode: " + mBufferMode); + } } mCallback.onInputBufferAvailable(mCodec, index); break; @@ -1747,7 +1770,26 @@ final public class MediaCodec { int index = msg.arg2; BufferInfo info = (MediaCodec.BufferInfo) msg.obj; synchronized(mBufferLock) { - validateOutputByteBuffer(mCachedOutputBuffers, index, info); + switch (mBufferMode) { + case BUFFER_MODE_LEGACY: + validateOutputByteBuffer(mCachedOutputBuffers, index, info); + break; + case BUFFER_MODE_BLOCK: + while (mOutputFrames.size() <= index) { + mOutputFrames.add(null); + } + OutputFrame frame = mOutputFrames.get(index); + if (frame == null) { + frame = new OutputFrame(index); + mOutputFrames.set(index, frame); + } + frame.setBufferInfo(info); + frame.setAccessible(true); + break; + default: + throw new IllegalStateException( + "Unrecognized buffer mode: " + mBufferMode); + } } mCallback.onOutputBufferAvailable( mCodec, index, info); @@ -1913,8 +1955,33 @@ final public class MediaCodec { */ public static final int CONFIGURE_FLAG_ENCODE = 1; + /** + * If this codec is to be used with {@link LinearBlock} and/or {@link + * GraphicBlock}, pass this flag. + * <p> + * When this flag is set, the following APIs throw IllegalStateException. + * <ul> + * <li>{@link #getInputBuffer} + * <li>{@link #getInputImage} + * <li>{@link #getInputBuffers} + * <li>{@link #getOutputBuffer} + * <li>{@link #getOutputImage} + * <li>{@link #getOutputBuffers} + * <li>{@link #queueInputBuffer} + * <li>{@link #queueSecureInputBuffer} + * <li>{@link #dequeueInputBuffer} + * <li>{@link #dequeueOutputBuffer} + * </ul> + */ + public static final int CONFIGURE_FLAG_USE_BLOCK_MODEL = 2; + /** @hide */ - @IntDef(flag = true, value = { CONFIGURE_FLAG_ENCODE }) + @IntDef( + flag = true, + value = { + CONFIGURE_FLAG_ENCODE, + CONFIGURE_FLAG_USE_BLOCK_MODEL, + }) @Retention(RetentionPolicy.SOURCE) public @interface ConfigureFlag {} @@ -1984,6 +2051,11 @@ final public class MediaCodec { descrambler != null ? descrambler.getBinder() : null, flags); } + private static final int BUFFER_MODE_INVALID = -1; + private static final int BUFFER_MODE_LEGACY = 0; + private static final int BUFFER_MODE_BLOCK = 1; + private int mBufferMode = BUFFER_MODE_INVALID; + private void configure( @Nullable MediaFormat format, @Nullable Surface surface, @Nullable MediaCrypto crypto, @Nullable IHwBinder descramblerBinder, @@ -2022,6 +2094,13 @@ final public class MediaCodec { mHasSurface = surface != null; mCrypto = crypto; + synchronized (mBufferLock) { + if ((flags & CONFIGURE_FLAG_USE_BLOCK_MODEL) != 0) { + mBufferMode = BUFFER_MODE_BLOCK; + } else { + mBufferMode = BUFFER_MODE_LEGACY; + } + } native_configure(keys, values, surface, crypto, descramblerBinder, flags); } @@ -2446,6 +2525,9 @@ final public class MediaCodec { int offset, int size, long presentationTimeUs, int flags) throws CryptoException { synchronized(mBufferLock) { + if (mBufferMode != BUFFER_MODE_LEGACY) { + throw new IllegalStateException(); + } invalidateByteBuffer(mCachedInputBuffers, index); mDequeuedInputBuffers.remove(index); } @@ -2695,6 +2777,9 @@ final public class MediaCodec { long presentationTimeUs, int flags) throws CryptoException { synchronized(mBufferLock) { + if (mBufferMode != BUFFER_MODE_LEGACY) { + throw new IllegalStateException(); + } invalidateByteBuffer(mCachedInputBuffers, index); mDequeuedInputBuffers.remove(index); } @@ -2726,6 +2811,11 @@ final public class MediaCodec { * @throws MediaCodec.CodecException upon codec error. */ public final int dequeueInputBuffer(long timeoutUs) { + synchronized (mBufferLock) { + if (mBufferMode != BUFFER_MODE_LEGACY) { + throw new IllegalStateException(); + } + } int res = native_dequeueInputBuffer(timeoutUs); if (res >= 0) { synchronized(mBufferLock) { @@ -2738,6 +2828,654 @@ final public class MediaCodec { private native final int native_dequeueInputBuffer(long timeoutUs); /** + * Section of memory that represents a linear block. Applications may + * acquire a block via {@link LinearBlock#obtain} and queue all or part + * of the block as an input buffer to a codec, or get a block allocated by + * codec as an output buffer from {@link OutputFrame}. + * + * {@see QueueRequest#setLinearBlock} + * {@see QueueRequest#setEncryptedLinearBlock} + * {@see OutputFrame#getLinearBlock} + */ + public static final class LinearBlock { + // No public constructors. + private LinearBlock() {} + + /** + * Returns true if the buffer is mappable. + * @throws IllegalStateException if invalid + */ + public boolean isMappable() { + synchronized (mLock) { + if (!mValid) { + throw new IllegalStateException(); + } + return mMappable; + } + } + + /** + * Map the memory and return the mapped region. + * <p> + * The returned memory region becomes inaccessible after + * {@link #recycle}, or the buffer is queued to the codecs and not + * returned to the client yet. + * + * @return mapped memory region as {@link ByteBuffer} object + * @throws IllegalStateException if not mappable or invalid + */ + public @NonNull ByteBuffer map() { + synchronized (mLock) { + if (!mValid) { + throw new IllegalStateException(); + } + if (!mMappable) { + throw new IllegalStateException(); + } + if (mMapped == null) { + mMapped = native_map(); + } + return mMapped; + } + } + + private native ByteBuffer native_map(); + + /** + * Mark this block as ready to be recycled by the framework once it is + * no longer in use. All operations to this object after + * this call will cause exceptions, as well as attempt to access the + * previously mapped memory region. Caller should clear all references + * to this object after this call. + * <p> + * To avoid excessive memory consumption, it is recommended that callers + * recycle buffers as soon as they no longer need the buffers + * + * @throws IllegalStateException if invalid + */ + public void recycle() { + synchronized (mLock) { + if (!mValid) { + throw new IllegalStateException(); + } + if (mMapped != null) { + mMapped.setAccessible(false); + mMapped = null; + } + native_recycle(); + mValid = false; + mNativeContext = 0; + } + sPool.offer(this); + } + + private native void native_recycle(); + + private native void native_obtain(int capacity, String[] codecNames); + + @Override + protected void finalize() { + native_recycle(); + } + + /** + * Returns true if it is possible to allocate a linear block that can be + * passed to all listed codecs as input buffers without copying the + * content. + * <p> + * Note that even if this function returns true, {@link #obtain} may + * still throw due to invalid arguments or allocation failure. + * + * @param codecNames list of codecs that the client wants to use a + * linear block without copying. Null entries are + * ignored. + */ + public static boolean isCodecCopyFreeCompatible(@NonNull String[] codecNames) { + return native_checkCompatible(codecNames); + } + + private static native boolean native_checkCompatible(@NonNull String[] codecNames); + + /** + * Obtain a linear block object no smaller than {@code capacity}. + * If {@link #isCodecCopyFreeCompatible} with the same + * {@code codecNames} returned true, the returned + * {@link LinearBlock} object can be queued to the listed codecs without + * copying. The returned {@link LinearBlock} object is always + * read/write mappable. + * + * @param capacity requested capacity of the linear block in bytes + * @param codecNames list of codecs that the client wants to use this + * linear block without copying. Null entries are + * ignored. + * @return a linear block object. + * @throws IllegalArgumentException if the capacity is invalid or + * codecNames contains invalid name + * @throws IOException if an error occurred while allocating a buffer + */ + public static @Nullable LinearBlock obtain( + int capacity, @NonNull String[] codecNames) { + LinearBlock buffer = sPool.poll(); + if (buffer == null) { + buffer = new LinearBlock(); + } + synchronized (buffer.mLock) { + buffer.native_obtain(capacity, codecNames); + } + return buffer; + } + + // Called from native + private void setInternalStateLocked(long context, boolean isMappable) { + mNativeContext = context; + mMappable = isMappable; + mValid = (context != 0); + } + + private static final BlockingQueue<LinearBlock> sPool = + new LinkedBlockingQueue<>(); + + private final Object mLock = new Object(); + private boolean mValid = false; + private boolean mMappable = false; + private ByteBuffer mMapped = null; + private long mNativeContext = 0; + } + + /** + * Section of memory that represents a graphic block. Applications may + * acquire a block via {@link GraphicBlock#obtain} and queue + * the block as an input buffer to a codec, or get a block allocated by + * codec as an output buffer from {@link OutputFrame}. + * + * {@see QueueRequest#setGraphicBlock} + * {@see OutputFrame#getGraphicBlock} + */ + public static final class GraphicBlock { + // No public constructors. + private GraphicBlock() {} + + /** + * Returns true if the buffer is mappable. + * @throws IllegalStateException if invalid + */ + public boolean isMappable() { + synchronized (mLock) { + if (!mValid) { + throw new IllegalStateException(); + } + return mMappable; + } + } + + /** + * Map the memory and return the mapped region. + * <p> + * Calling {@link #recycle} or + * {@link QueueRequest#setGraphicBlock} causes the returned + * {@link Image} object to be closed, if not already. + * + * @return mapped memory region as {@link Image} object + * @throws IllegalStateException if not mappable or invalid + */ + public @NonNull Image map() { + synchronized (mLock) { + if (!mValid) { + throw new IllegalStateException(); + } + if (!mMappable) { + throw new IllegalStateException(); + } + if (mMapped == null) { + mMapped = native_map(); + } + return mMapped; + } + } + + private native Image native_map(); + + /** + * Mark this block as ready to be recycled by the framework once it is + * no longer in use. All operations to this object after + * this call will cause exceptions, as well as attempt to access the + * previously mapped memory region. Caller should clear all references + * to this object after this call. + * <p> + * To avoid excessive memory consumption, it is recommended that callers + * recycle buffers as soon as they no longer need the buffers. + * + * @throws IllegalStateException if invalid + */ + public void recycle() { + synchronized (mLock) { + if (!mValid) { + throw new IllegalStateException(); + } + if (mMapped != null) { + mMapped.close(); + mMapped = null; + } + native_recycle(); + mValid = false; + mNativeContext = 0; + } + sPool.offer(this); + } + + private native void native_recycle(); + + /** + * Returns true if it is possible to allocate a graphic block that + * can be passed to all listed codecs as an input buffer without + * copying. + * <p> + * Note that even if this function returns true, {@link #obtain} + * may still throw due to invalid arguments or allocation failure. + * In addition, choosing a format that is not natively supported by the + * codec may cause color conversion. + * + * @param codecNames list of codecs that the client wants to use a + * graphic block without copying. Null entries are + * ignored. + */ + public static boolean isCodecCopyFreeCompatible(@NonNull String[] codecNames) { + return native_checkCompatible(codecNames); + } + + private static native boolean native_checkCompatible(@NonNull String[] codecNames); + + // Called from native + private void setInternalStateLocked(long context, boolean isMappable) { + mNativeContext = context; + mMappable = isMappable; + mValid = (context != 0); + } + + private static final BlockingQueue<GraphicBlock> sPool = + new LinkedBlockingQueue<>(); + + /** + * Obtain a graphic block object of dimension + * {@code width}x{@code height}. + * If {@link #isCodecCopyFreeCompatible} with the same + * {@code codecNames} returned true, the returned + * {@link GraphicBlock} object can be queued to the listed codecs + * without copying. The returned {@link GraphicBlock} object is always + * read/write mappable. + * + * @param width requested width of the graphic block + * @param height requested height of the graphic block + * @param format the format of pixels. One of the {@code COLOR_Format} + * values from {@link MediaCodecInfo.CodecCapabilities}. + * @param usage the usage of the buffer. @HardwareBuffer.Usage + * @param codecNames list of codecs that the client wants to use this + * graphic block without copying. Null entries are + * ignored. + * @return a graphic block object. + * @throws IllegalArgumentException if the parameters are invalid or + * not supported + * @throws IOException if an error occurred while allocating a buffer + */ + public static @NonNull GraphicBlock obtain( + int width, + int height, + int format, + @HardwareBuffer.Usage long usage, + @NonNull String[] codecNames) { + GraphicBlock buffer = sPool.poll(); + if (buffer == null) { + buffer = new GraphicBlock(); + } + if (width < 0 || height < 0) { + throw new IllegalArgumentException(); + } + synchronized (buffer.mLock) { + buffer.native_obtain(width, height, format, usage, codecNames); + } + return buffer; + } + + private native void native_obtain( + int width, + int height, + int format, + @HardwareBuffer.Usage long usage, + @NonNull String[] codecNames); + + @Override + protected void finalize() { + native_recycle(); + } + + private final Object mLock = new Object(); + private boolean mValid = false; + private boolean mMappable = false; + private Image mMapped = null; + private long mNativeContext = 0; + } + + /** + * Builder-like class for queue requests. Use this class to prepare a + * queue request and send it. + */ + public final class QueueRequest { + // No public constructor + private QueueRequest(@NonNull MediaCodec codec, int index) { + mCodec = codec; + mIndex = index; + } + + /** + * Set a linear block to this queue request. Exactly one buffer must be + * set for a queue request before calling {@link #queue}. It is possible + * to use the same {@link LinearBlock} object for multiple queue + * requests. The behavior is undefined if the range of the buffer + * overlaps for multiple requests, or the application writes into the + * region being processed by the codec. + * + * @param block The linear block object + * @param offset The byte offset into the input buffer at which the data starts. + * @param size The number of bytes of valid input data. + * @param presentationTimeUs The presentation timestamp in microseconds for this + * buffer. This is normally the media time at which this + * buffer should be presented (rendered). When using an output + * surface, this will be propagated as the {@link + * SurfaceTexture#getTimestamp timestamp} for the frame (after + * conversion to nanoseconds). + * @param flags A bitmask of flags + * {@link #BUFFER_FLAG_CODEC_CONFIG} and {@link #BUFFER_FLAG_END_OF_STREAM}. + * While not prohibited, most codecs do not use the + * {@link #BUFFER_FLAG_KEY_FRAME} flag for input buffers. + * @return this object + * @throws IllegalStateException if a buffer is already set + */ + public @NonNull QueueRequest setLinearBlock( + @NonNull LinearBlock block, + int offset, + int size, + long presentationTimeUs, + @BufferFlag int flags) { + if (!isAccessible()) { + throw new IllegalStateException(); + } + if (mLinearBlock != null || mGraphicBlock != null) { + throw new IllegalStateException(); + } + mLinearBlock = block; + mOffset = offset; + mSize = size; + mPresentationTimeUs = presentationTimeUs; + mFlags = flags; + return this; + } + + /** + * Set an encrypted linear block to this queue request. Exactly one + * buffer must be set for a queue request before calling {@link #queue}. + * + * @param block The linear block object + * @param offset The byte offset into the input buffer at which the data starts. + * @param presentationTimeUs The presentation timestamp in microseconds for this + * buffer. This is normally the media time at which this + * buffer should be presented (rendered). When using an output + * surface, this will be propagated as the {@link + * SurfaceTexture#getTimestamp timestamp} for the frame (after + * conversion to nanoseconds). + * @param cryptoInfo Metadata describing the structure of the encrypted input sample. + * @param flags A bitmask of flags + * {@link #BUFFER_FLAG_CODEC_CONFIG} and {@link #BUFFER_FLAG_END_OF_STREAM}. + * While not prohibited, most codecs do not use the + * {@link #BUFFER_FLAG_KEY_FRAME} flag for input buffers. + * @return this object + * @throws IllegalStateException if a buffer is already set + */ + public @NonNull QueueRequest setEncryptedLinearBlock( + @NonNull LinearBlock block, + int offset, + @NonNull MediaCodec.CryptoInfo cryptoInfo, + long presentationTimeUs, + @BufferFlag int flags) { + if (!isAccessible()) { + throw new IllegalStateException(); + } + if (mLinearBlock != null || mGraphicBlock != null) { + throw new IllegalStateException(); + } + mLinearBlock = block; + mOffset = offset; + mCryptoInfo = cryptoInfo; + mPresentationTimeUs = presentationTimeUs; + mFlags = flags; + return this; + } + + /** + * Set a graphic block to this queue request. Exactly one buffer must + * be set for a queue request before calling {@link #queue}. + * + * @param block The graphic block object + * @param presentationTimeUs The presentation timestamp in microseconds for this + * buffer. This is normally the media time at which this + * buffer should be presented (rendered). When using an output + * surface, this will be propagated as the {@link + * SurfaceTexture#getTimestamp timestamp} for the frame (after + * conversion to nanoseconds). + * @param flags A bitmask of flags + * {@link #BUFFER_FLAG_CODEC_CONFIG} and {@link #BUFFER_FLAG_END_OF_STREAM}. + * While not prohibited, most codecs do not use the + * {@link #BUFFER_FLAG_KEY_FRAME} flag for input buffers. + * @return this object + * @throws IllegalStateException if a buffer is already set + */ + public @NonNull QueueRequest setGraphicBlock( + @NonNull GraphicBlock block, + long presentationTimeUs, + @BufferFlag int flags) { + if (!isAccessible()) { + throw new IllegalStateException(); + } + if (mLinearBlock != null || mGraphicBlock != null) { + throw new IllegalStateException(); + } + mGraphicBlock = block; + mPresentationTimeUs = presentationTimeUs; + mFlags = flags; + return this; + } + + /** + * Add a integer parameter. See {@link MediaFormat} for the list of + * supported tunings. If there was {@link MediaCodec#setParameters} + * call with the same key which is not processed by the codec yet, the + * value set from this method will override the unprocessed value. + */ + public @NonNull QueueRequest setIntegerParameter( + @NonNull String key, int value) { + if (!isAccessible()) { + throw new IllegalStateException(); + } + mTuningKeys.add(key); + mTuningValues.add(Integer.valueOf(value)); + return this; + } + + /** + * Add a long parameter. See {@link MediaFormat} for the list of + * supported tunings. If there was {@link MediaCodec#setParameters} + * call with the same key which is not processed by the codec yet, the + * value set from this method will override the unprocessed value. + */ + public @NonNull QueueRequest setLongParameter( + @NonNull String key, long value) { + if (!isAccessible()) { + throw new IllegalStateException(); + } + mTuningKeys.add(key); + mTuningValues.add(Long.valueOf(value)); + return this; + } + + /** + * Add a float parameter. See {@link MediaFormat} for the list of + * supported tunings. If there was {@link MediaCodec#setParameters} + * call with the same key which is not processed by the codec yet, the + * value set from this method will override the unprocessed value. + */ + public @NonNull QueueRequest setFloatParameter( + @NonNull String key, float value) { + if (!isAccessible()) { + throw new IllegalStateException(); + } + mTuningKeys.add(key); + mTuningValues.add(Float.valueOf(value)); + return this; + } + + /** + * Add a {@link ByteBuffer} parameter. See {@link MediaFormat} for the list of + * supported tunings. If there was {@link MediaCodec#setParameters} + * call with the same key which is not processed by the codec yet, the + * value set from this method will override the unprocessed value. + */ + public @NonNull QueueRequest setByteBufferParameter( + @NonNull String key, @NonNull ByteBuffer value) { + if (!isAccessible()) { + throw new IllegalStateException(); + } + mTuningKeys.add(key); + mTuningValues.add(value); + return this; + } + + /** + * Add a string parameter. See {@link MediaFormat} for the list of + * supported tunings. If there was {@link MediaCodec#setParameters} + * call with the same key which is not processed by the codec yet, the + * value set from this method will override the unprocessed value. + */ + public @NonNull QueueRequest setStringParameter( + @NonNull String key, @NonNull String value) { + if (!isAccessible()) { + throw new IllegalStateException(); + } + mTuningKeys.add(key); + mTuningValues.add(value); + return this; + } + + /** + * Finish building a queue request and queue the buffers with tunings. + */ + public void queue() { + if (!isAccessible()) { + throw new IllegalStateException(); + } + if (mLinearBlock == null && mGraphicBlock == null) { + throw new IllegalStateException(); + } + setAccessible(false); + if (mLinearBlock != null) { + mCodec.native_queueLinearBlock( + mIndex, mLinearBlock, mOffset, mSize, mCryptoInfo, + mPresentationTimeUs, mFlags, + mTuningKeys, mTuningValues); + } else if (mGraphicBlock != null) { + mCodec.native_queueGraphicBlock( + mIndex, mGraphicBlock, mPresentationTimeUs, mFlags, + mTuningKeys, mTuningValues); + } + clear(); + } + + @NonNull QueueRequest clear() { + mLinearBlock = null; + mOffset = 0; + mSize = 0; + mCryptoInfo = null; + mGraphicBlock = null; + mPresentationTimeUs = 0; + mFlags = 0; + mTuningKeys.clear(); + mTuningValues.clear(); + return this; + } + + boolean isAccessible() { + return mAccessible; + } + + @NonNull QueueRequest setAccessible(boolean accessible) { + mAccessible = accessible; + return this; + } + + private final MediaCodec mCodec; + private final int mIndex; + private LinearBlock mLinearBlock = null; + private int mOffset = 0; + private int mSize = 0; + private MediaCodec.CryptoInfo mCryptoInfo = null; + private GraphicBlock mGraphicBlock = null; + private long mPresentationTimeUs = 0; + private @BufferFlag int mFlags = 0; + private final ArrayList<String> mTuningKeys = new ArrayList<>(); + private final ArrayList<Object> mTuningValues = new ArrayList<>(); + + private boolean mAccessible = false; + } + + private native void native_queueLinearBlock( + int index, + @NonNull LinearBlock block, + int offset, + int size, + @Nullable CryptoInfo cryptoInfo, + long presentationTimeUs, + int flags, + @NonNull ArrayList<String> keys, + @NonNull ArrayList<Object> values); + + private native void native_queueGraphicBlock( + int index, + @NonNull GraphicBlock block, + long presentationTimeUs, + int flags, + @NonNull ArrayList<String> keys, + @NonNull ArrayList<Object> values); + + private final ArrayList<QueueRequest> mQueueRequests = new ArrayList<>(); + + /** + * Return a clear {@link QueueRequest} object for an input slot index. + * + * @param index input slot index from + * {@link Callback#onInputBufferAvailable} + * @return queue request object + * @throws IllegalStateException if not using block model + * @throws IllegalArgumentException if the input slot is not available or + * the index is out of range + */ + public @NonNull QueueRequest getQueueRequest(int index) { + synchronized (mBufferLock) { + if (mBufferMode != BUFFER_MODE_BLOCK) { + throw new IllegalStateException(); + } + if (index < 0 || index >= mQueueRequests.size()) { + throw new IllegalArgumentException(); + } + QueueRequest request = mQueueRequests.get(index); + if (request == null) { + throw new IllegalArgumentException(); + } + if (!request.isAccessible()) { + throw new IllegalArgumentException(); + } + return request.clear(); + } + } + + /** * If a non-negative timeout had been specified in the call * to {@link #dequeueOutputBuffer}, indicates that the call timed out. */ @@ -2789,8 +3527,13 @@ final public class MediaCodec { @OutputBufferInfo public final int dequeueOutputBuffer( @NonNull BufferInfo info, long timeoutUs) { + synchronized (mBufferLock) { + if (mBufferMode != BUFFER_MODE_LEGACY) { + throw new IllegalStateException(); + } + } int res = native_dequeueOutputBuffer(info, timeoutUs); - synchronized(mBufferLock) { + synchronized (mBufferLock) { if (res == INFO_OUTPUT_BUFFERS_CHANGED) { cacheBuffers(false /* input */); } else if (res >= 0) { @@ -2826,15 +3569,7 @@ final public class MediaCodec { * @throws MediaCodec.CodecException upon codec error. */ public final void releaseOutputBuffer(int index, boolean render) { - BufferInfo info = null; - synchronized(mBufferLock) { - invalidateByteBuffer(mCachedOutputBuffers, index); - mDequeuedOutputBuffers.remove(index); - if (mHasSurface) { - info = mDequeuedOutputInfos.remove(index); - } - } - releaseOutputBuffer(index, render, false /* updatePTS */, 0 /* dummy */); + releaseOutputBufferInternal(index, render, false /* updatePTS */, 0 /* dummy */); } /** @@ -2887,16 +3622,33 @@ final public class MediaCodec { * @throws MediaCodec.CodecException upon codec error. */ public final void releaseOutputBuffer(int index, long renderTimestampNs) { + releaseOutputBufferInternal( + index, true /* render */, true /* updatePTS */, renderTimestampNs); + } + + private void releaseOutputBufferInternal( + int index, boolean render, boolean updatePts, long renderTimestampNs) { BufferInfo info = null; synchronized(mBufferLock) { - invalidateByteBuffer(mCachedOutputBuffers, index); - mDequeuedOutputBuffers.remove(index); - if (mHasSurface) { - info = mDequeuedOutputInfos.remove(index); + switch (mBufferMode) { + case BUFFER_MODE_LEGACY: + invalidateByteBuffer(mCachedOutputBuffers, index); + mDequeuedOutputBuffers.remove(index); + if (mHasSurface) { + info = mDequeuedOutputInfos.remove(index); + } + break; + case BUFFER_MODE_BLOCK: + OutputFrame frame = mOutputFrames.get(index); + frame.setAccessible(false); + frame.clear(); + break; + default: + throw new IllegalStateException(); } } releaseOutputBuffer( - index, true /* render */, true /* updatePTS */, renderTimestampNs); + index, render, updatePts, renderTimestampNs); } @UnsupportedAppUsage @@ -3116,6 +3868,8 @@ final public class MediaCodec { mCachedOutputBuffers = null; mDequeuedInputBuffers.clear(); mDequeuedOutputBuffers.clear(); + mQueueRequests.clear(); + mOutputFrames.clear(); } } @@ -3154,11 +3908,16 @@ final public class MediaCodec { */ @NonNull public ByteBuffer[] getInputBuffers() { - if (mCachedInputBuffers == null) { - throw new IllegalStateException(); + synchronized (mBufferLock) { + if (mBufferMode != BUFFER_MODE_LEGACY) { + throw new IllegalStateException(); + } + if (mCachedInputBuffers == null) { + throw new IllegalStateException(); + } + // FIXME: check codec status + return mCachedInputBuffers; } - // FIXME: check codec status - return mCachedInputBuffers; } /** @@ -3185,11 +3944,16 @@ final public class MediaCodec { */ @NonNull public ByteBuffer[] getOutputBuffers() { - if (mCachedOutputBuffers == null) { - throw new IllegalStateException(); + synchronized (mBufferLock) { + if (mBufferMode != BUFFER_MODE_LEGACY) { + throw new IllegalStateException(); + } + if (mCachedOutputBuffers == null) { + throw new IllegalStateException(); + } + // FIXME: check codec status + return mCachedOutputBuffers; } - // FIXME: check codec status - return mCachedOutputBuffers; } /** @@ -3212,8 +3976,13 @@ final public class MediaCodec { */ @Nullable public ByteBuffer getInputBuffer(int index) { + synchronized (mBufferLock) { + if (mBufferMode != BUFFER_MODE_LEGACY) { + throw new IllegalStateException(); + } + } ByteBuffer newBuffer = getBuffer(true /* input */, index); - synchronized(mBufferLock) { + synchronized (mBufferLock) { invalidateByteBuffer(mCachedInputBuffers, index); mDequeuedInputBuffers.put(index, newBuffer); } @@ -3241,8 +4010,13 @@ final public class MediaCodec { */ @Nullable public Image getInputImage(int index) { + synchronized (mBufferLock) { + if (mBufferMode != BUFFER_MODE_LEGACY) { + throw new IllegalStateException(); + } + } Image newImage = getImage(true /* input */, index); - synchronized(mBufferLock) { + synchronized (mBufferLock) { invalidateByteBuffer(mCachedInputBuffers, index); mDequeuedInputBuffers.put(index, newImage); } @@ -3270,8 +4044,13 @@ final public class MediaCodec { */ @Nullable public ByteBuffer getOutputBuffer(int index) { + synchronized (mBufferLock) { + if (mBufferMode != BUFFER_MODE_LEGACY) { + throw new IllegalStateException(); + } + } ByteBuffer newBuffer = getBuffer(false /* input */, index); - synchronized(mBufferLock) { + synchronized (mBufferLock) { invalidateByteBuffer(mCachedOutputBuffers, index); mDequeuedOutputBuffers.put(index, newBuffer); } @@ -3298,8 +4077,13 @@ final public class MediaCodec { */ @Nullable public Image getOutputImage(int index) { + synchronized (mBufferLock) { + if (mBufferMode != BUFFER_MODE_LEGACY) { + throw new IllegalStateException(); + } + } Image newImage = getImage(false /* input */, index); - synchronized(mBufferLock) { + synchronized (mBufferLock) { invalidateByteBuffer(mCachedOutputBuffers, index); mDequeuedOutputBuffers.put(index, newImage); } @@ -3307,6 +4091,149 @@ final public class MediaCodec { } /** + * A single output frame and its associated metadata. + */ + public static final class OutputFrame { + // No public constructor + OutputFrame(int index) { + mIndex = index; + } + + /** + * Returns the output linear block, or null if this frame is empty. + * + * @throws IllegalStateException if this output frame is not linear. + */ + public @Nullable LinearBlock getLinearBlock() { + if (mGraphicBlock != null) { + throw new IllegalStateException(); + } + return mLinearBlock; + } + + /** + * Returns the output graphic block, or null if this frame is empty. + * + * @throws IllegalStateException if this output frame is not graphic. + */ + public @Nullable GraphicBlock getGraphicBlock() { + if (mLinearBlock != null) { + throw new IllegalStateException(); + } + return mGraphicBlock; + } + + /** + * Returns the presentation timestamp in microseconds. + */ + public long getPresentationTimeUs() { + return mPresentationTimeUs; + } + + /** + * Returns the buffer flags. + */ + public @BufferFlag int getFlags() { + return mFlags; + } + + /** + * Returns a read-only {@link MediaFormat} for this frame. The returned + * object is valid only while the client is holding the output frame. + */ + public @NonNull MediaFormat getFormat() { + return mFormat; + } + + /** + * Populate {@code keys} with the name of entries that has changed from + * the previous frame. The entries may have been removed/changed/added. + * Client can find out what the change is by querying {@link MediaFormat} + * object returned from {@link #getFormat}. + */ + public void getChangedKeys(@NonNull Set<String> keys) { + keys.clear(); + keys.addAll(mChangedKeys); + } + + void clear() { + mLinearBlock = null; + mGraphicBlock = null; + mFormat = null; + mChangedKeys.clear(); + mLoaded = false; + } + + boolean isAccessible() { + return mAccessible; + } + + void setAccessible(boolean accessible) { + mAccessible = accessible; + } + + void setBufferInfo(MediaCodec.BufferInfo info) { + mPresentationTimeUs = info.presentationTimeUs; + mFlags = info.flags; + } + + boolean isLoaded() { + return mLoaded; + } + + void setLoaded(boolean loaded) { + mLoaded = loaded; + } + + private final int mIndex; + private LinearBlock mLinearBlock = null; + private GraphicBlock mGraphicBlock = null; + private long mPresentationTimeUs = 0; + private @BufferFlag int mFlags = 0; + private MediaFormat mFormat = null; + private final ArrayList<String> mChangedKeys = new ArrayList<>(); + private boolean mAccessible = false; + private boolean mLoaded = false; + } + + private final ArrayList<OutputFrame> mOutputFrames = new ArrayList<>(); + + /** + * Returns an {@link OutputFrame} object. + * + * @param index output buffer index from + * {@link Callback#onOutputBufferAvailable} + * @return {@link OutputFrame} object describing the output buffer + * @throws IllegalStateException if not using block model + * @throws IllegalArgumentException if the output buffer is not available or + * the index is out of range + */ + public @NonNull OutputFrame getOutputFrame(int index) { + synchronized (mBufferLock) { + if (mBufferMode != BUFFER_MODE_BLOCK) { + throw new IllegalStateException(); + } + if (index < 0 || index >= mOutputFrames.size()) { + throw new IllegalArgumentException(); + } + OutputFrame frame = mOutputFrames.get(index); + if (frame == null) { + throw new IllegalArgumentException(); + } + if (!frame.isAccessible()) { + throw new IllegalArgumentException(); + } + if (!frame.isLoaded()) { + native_getOutputFrame(frame, index); + frame.setLoaded(true); + } + return frame; + } + } + + private native void native_getOutputFrame(OutputFrame frame, int index); + + /** * The content is scaled to the surface dimensions */ public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = 1; @@ -3889,7 +4816,9 @@ final public class MediaCodec { @Override public void close() { if (mIsImageValid) { - java.nio.NioUtils.freeDirectBuffer(mBuffer); + if (mBuffer != null) { + java.nio.NioUtils.freeDirectBuffer(mBuffer); + } mIsImageValid = false; } } @@ -3908,7 +4837,6 @@ final public class MediaCodec { super.setCropRect(cropRect); } - public MediaImage( @NonNull ByteBuffer buffer, @NonNull ByteBuffer info, boolean readOnly, long timestamp, int xOffset, int yOffset, @Nullable Rect cropRect) { @@ -3963,7 +4891,6 @@ final public class MediaCodec { throw new UnsupportedOperationException("unexpected strides: " + colInc + " pixel, " + rowInc + " row on plane " + ix); } - buffer.clear(); buffer.position(mBuffer.position() + planeOffset + (xOffset / horiz) * colInc + (yOffset / vert) * rowInc); @@ -3983,6 +4910,30 @@ final public class MediaCodec { super.setCropRect(cropRect); } + public MediaImage( + @NonNull Image.Plane[] planes, int width, int height, int format, boolean readOnly, + long timestamp, int xOffset, int yOffset, @Nullable Rect cropRect) { + mWidth = width; + mHeight = height; + mFormat = format; + mTimestamp = timestamp; + mIsImageValid = true; + mIsReadOnly = readOnly; + mBuffer = null; + mInfo = null; + mPlanes = planes; + + // save offsets and info + mXOffset = xOffset; + mYOffset = yOffset; + + if (cropRect == null) { + cropRect = new Rect(0, 0, mWidth, mHeight); + } + cropRect.offset(-xOffset, -yOffset); + super.setCropRect(cropRect); + } + private class MediaPlane extends Plane { public MediaPlane(@NonNull ByteBuffer buffer, int rowInc, int colInc) { mData = buffer; diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java index 1a0f13943694..f408ac344d7c 100644 --- a/media/java/android/media/MediaFormat.java +++ b/media/java/android/media/MediaFormat.java @@ -294,6 +294,14 @@ public final class MediaFormat { public static final String KEY_BIT_RATE = "bitrate"; /** + * A key describing the hardware AV sync id. + * The associated value is an integer + * + * @see android.media.tv.tuner.Tuner#getAvSyncHwId + */ + public static final String KEY_HARDWARE_AV_SYNC_ID = "hw-av-sync-id"; + + /** * A key describing the max bitrate in bits/sec. * This is usually over a one-second sliding window (e.g. over any window of one second). * The associated value is an integer diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index 5cb32d6ab550..61e2f77b7d2e 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -178,8 +178,9 @@ public class MediaRouter2Manager { /** * Gets routing controllers of an application with the given package name. - * If the application isn't running or it doesn't use {@link MediaRouter2}, an empty list - * will be returned. + * The first element of the returned list is the system routing controller. + * + * @see MediaRouter2#getSystemController() */ @NonNull public List<RoutingController> getRoutingControllers(@NonNull String packageName) { @@ -188,7 +189,8 @@ public class MediaRouter2Manager { List<RoutingController> controllers = new ArrayList<>(); for (RoutingSessionInfo sessionInfo : getActiveSessions()) { - if (TextUtils.equals(sessionInfo.getClientPackageName(), packageName)) { + if (sessionInfo.isSystemSession() + || TextUtils.equals(sessionInfo.getClientPackageName(), packageName)) { controllers.add(new RoutingController(sessionInfo)); } } @@ -196,8 +198,11 @@ public class MediaRouter2Manager { } /** - * Gets the list of all active routing sessions. It doesn't include default routing sessions - * of applications. + * Gets the list of all active routing sessions. + * The first element of the list is the system routing session containing + * phone speakers, wired headset, Bluetooth devices. + * The system routing session is shared by apps such that controlling it will affect + * all apps. */ @NonNull public List<RoutingSessionInfo> getActiveSessions() { diff --git a/media/java/android/media/audiopolicy/AudioMixingRule.java b/media/java/android/media/audiopolicy/AudioMixingRule.java index 8c204d222cd4..bca3fa7834b4 100644 --- a/media/java/android/media/audiopolicy/AudioMixingRule.java +++ b/media/java/android/media/audiopolicy/AudioMixingRule.java @@ -73,6 +73,12 @@ public class AudioMixingRule { * parameter is an instance of {@link java.lang.Integer}. */ public static final int RULE_MATCH_UID = 0x1 << 2; + /** + * A rule requiring the userId of the audio stream to match that specified. + * This mixing rule can be added with {@link Builder#addMixRule(int, Object)} where the Object + * parameter is an instance of {@link java.lang.Integer}. + */ + public static final int RULE_MATCH_USERID = 0x1 << 3; private final static int RULE_EXCLUSION_MASK = 0x8000; /** @@ -94,6 +100,13 @@ public class AudioMixingRule { public static final int RULE_EXCLUDE_UID = RULE_EXCLUSION_MASK | RULE_MATCH_UID; + /** + * @hide + * A rule requiring the userId information to differ. + */ + public static final int RULE_EXCLUDE_USERID = + RULE_EXCLUSION_MASK | RULE_MATCH_USERID; + /** @hide */ public static final class AudioMixMatchCriterion { @UnsupportedAppUsage @@ -125,19 +138,20 @@ public class AudioMixingRule { dest.writeInt(mRule); final int match_rule = mRule & ~RULE_EXCLUSION_MASK; switch (match_rule) { - case RULE_MATCH_ATTRIBUTE_USAGE: - dest.writeInt(mAttr.getUsage()); - break; - case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: - dest.writeInt(mAttr.getCapturePreset()); - break; - case RULE_MATCH_UID: - dest.writeInt(mIntProp); - break; - default: - Log.e("AudioMixMatchCriterion", "Unknown match rule" + match_rule - + " when writing to Parcel"); - dest.writeInt(-1); + case RULE_MATCH_ATTRIBUTE_USAGE: + dest.writeInt(mAttr.getUsage()); + break; + case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: + dest.writeInt(mAttr.getCapturePreset()); + break; + case RULE_MATCH_UID: + case RULE_MATCH_USERID: + dest.writeInt(mIntProp); + break; + default: + Log.e("AudioMixMatchCriterion", "Unknown match rule" + match_rule + + " when writing to Parcel"); + dest.writeInt(-1); } } @@ -203,6 +217,7 @@ public class AudioMixingRule { case RULE_MATCH_ATTRIBUTE_USAGE: case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: case RULE_MATCH_UID: + case RULE_MATCH_USERID: return true; default: return false; @@ -225,6 +240,7 @@ public class AudioMixingRule { case RULE_MATCH_ATTRIBUTE_USAGE: case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: case RULE_MATCH_UID: + case RULE_MATCH_USERID: return true; default: return false; @@ -234,11 +250,12 @@ public class AudioMixingRule { private static boolean isPlayerRule(int rule) { final int match_rule = rule & ~RULE_EXCLUSION_MASK; switch (match_rule) { - case RULE_MATCH_ATTRIBUTE_USAGE: - case RULE_MATCH_UID: - return true; - default: - return false; + case RULE_MATCH_ATTRIBUTE_USAGE: + case RULE_MATCH_UID: + case RULE_MATCH_USERID: + return true; + default: + return false; } } @@ -319,7 +336,8 @@ public class AudioMixingRule { * property to match against. * @param rule one of {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE}, * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or - * {@link AudioMixingRule#RULE_MATCH_UID}. + * {@link AudioMixingRule#RULE_MATCH_UID} or + * {@link AudioMixingRule#RULE_MATCH_USERID}. * @param property see the definition of each rule for the type to use (either an * {@link AudioAttributes} or an {@link java.lang.Integer}). * @return the same Builder instance. @@ -349,7 +367,8 @@ public class AudioMixingRule { * coming from the specified UID. * @param rule one of {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE}, * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or - * {@link AudioMixingRule#RULE_MATCH_UID}. + * {@link AudioMixingRule#RULE_MATCH_UID} or + * {@link AudioMixingRule#RULE_MATCH_USERID}. * @param property see the definition of each rule for the type to use (either an * {@link AudioAttributes} or an {@link java.lang.Integer}). * @return the same Builder instance. @@ -425,6 +444,8 @@ public class AudioMixingRule { * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or * {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET}, * {@link AudioMixingRule#RULE_MATCH_UID}, {@link AudioMixingRule#RULE_EXCLUDE_UID}. + * {@link AudioMixingRule#RULE_MATCH_USERID}, + * {@link AudioMixingRule#RULE_EXCLUDE_USERID}. * @return the same Builder instance. * @throws IllegalArgumentException */ @@ -495,6 +516,20 @@ public class AudioMixingRule { } } break; + case RULE_MATCH_USERID: + // "userid"-based rule + if (criterion.mIntProp == intProp.intValue()) { + if (criterion.mRule == rule) { + // rule already exists, we're done + return this; + } else { + // criterion already exists with a another rule, + // it is incompatible + throw new IllegalArgumentException("Contradictory rule exists" + + " for userId " + intProp); + } + } + break; } } // rule didn't exist, add it @@ -504,6 +539,7 @@ public class AudioMixingRule { mCriteria.add(new AudioMixMatchCriterion(attrToMatch, rule)); break; case RULE_MATCH_UID: + case RULE_MATCH_USERID: mCriteria.add(new AudioMixMatchCriterion(intProp, rule)); break; default: @@ -530,6 +566,7 @@ public class AudioMixingRule { .setInternalCapturePreset(preset).build(); break; case RULE_MATCH_UID: + case RULE_MATCH_USERID: intProp = new Integer(in.readInt()); break; default: diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java index 27f02fe528f3..32a4a4f70192 100644 --- a/media/java/android/media/audiopolicy/AudioPolicy.java +++ b/media/java/android/media/audiopolicy/AudioPolicy.java @@ -51,6 +51,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * @hide @@ -404,7 +405,7 @@ public class AudioPolicy { /** * @hide - * Configures the audio framework so that all audio stream originating from the given UID + * Configures the audio framework so that all audio streams originating from the given UID * can only come from a set of audio devices. * For this routing to be operational, a number of {@link AudioMix} instances must have been * previously registered on this policy, and routed to a super-set of the given audio devices @@ -476,6 +477,78 @@ public class AudioPolicy { } } + /** + * @hide + * Removes audio device affinity previously set by + * {@link #setUserIdDeviceAffinity(int, java.util.List)}. + * @param userId userId of the application affected. + * @return true if the change was successful, false otherwise. + */ + @TestApi + @SystemApi + public boolean removeUserIdDeviceAffinity(int userId) { + synchronized (mLock) { + if (mStatus != POLICY_STATUS_REGISTERED) { + throw new IllegalStateException("Cannot use unregistered AudioPolicy"); + } + final IAudioService service = getService(); + try { + final int status = service.removeUserIdDeviceAffinity(this.cb(), userId); + return (status == AudioManager.SUCCESS); + } catch (RemoteException e) { + Log.e(TAG, "Dead object in removeUserIdDeviceAffinity", e); + return false; + } + } + } + + /** + * @hide + * Configures the audio framework so that all audio streams originating from the given user + * can only come from a set of audio devices. + * For this routing to be operational, a number of {@link AudioMix} instances must have been + * previously registered on this policy, and routed to a super-set of the given audio devices + * with {@link AudioMix.Builder#setDevice(android.media.AudioDeviceInfo)}. Note that having + * multiple devices in the list doesn't imply the signals will be duplicated on the different + * audio devices, final routing will depend on the {@link AudioAttributes} of the sounds being + * played. + * @param userId Android user id to affect. + * @param devices list of devices to which the audio stream of the application may be routed. + * @return true if the change was successful, false otherwise. + */ + @TestApi + @SystemApi + public boolean setUserIdDeviceAffinity(int userId, @NonNull List<AudioDeviceInfo> devices) { + Objects.requireNonNull(devices, "Illegal null list of audio devices"); + synchronized (mLock) { + if (mStatus != POLICY_STATUS_REGISTERED) { + throw new IllegalStateException("Cannot use unregistered AudioPolicy"); + } + final int[] deviceTypes = new int[devices.size()]; + final String[] deviceAddresses = new String[devices.size()]; + int i = 0; + for (AudioDeviceInfo device : devices) { + if (device == null) { + throw new IllegalArgumentException( + "Illegal null AudioDeviceInfo in setUserIdDeviceAffinity"); + } + deviceTypes[i] = + AudioDeviceInfo.convertDeviceTypeToInternalDevice(device.getType()); + deviceAddresses[i] = device.getAddress(); + i++; + } + final IAudioService service = getService(); + try { + final int status = service.setUserIdDeviceAffinity(this.cb(), + userId, deviceTypes, deviceAddresses); + return (status == AudioManager.SUCCESS); + } catch (RemoteException e) { + Log.e(TAG, "Dead object in setUserIdDeviceAffinity", e); + return false; + } + } + } + public void setRegistration(String regId) { synchronized (mLock) { mRegistrationId = regId; diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java index c4ba0c1fc835..b048158c3979 100644 --- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java +++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java @@ -197,6 +197,14 @@ public class AudioPolicyConfig implements Parcelable { textDump += " exclude UID "; textDump += criterion.mIntProp; break; + case AudioMixingRule.RULE_MATCH_USERID: + textDump += " match userId "; + textDump += criterion.mIntProp; + break; + case AudioMixingRule.RULE_EXCLUDE_USERID: + textDump += " exclude userId "; + textDump += criterion.mIntProp; + break; default: textDump += "invalid rule!"; } diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java index 9953626f0a7a..870c1b4a3558 100644 --- a/media/java/android/media/session/MediaSession.java +++ b/media/java/android/media/session/MediaSession.java @@ -146,6 +146,8 @@ public final class MediaSession { * the system but will not be published until {@link #setActive(boolean) * setActive(true)} is called. You must call {@link #release()} when * finished with the session. + * <p> + * Note that {@link RuntimeException} will be thrown if an app creates too many sessions. * * @param context The context to use to create the session. * @param tag A short name for debugging purposes. @@ -163,6 +165,8 @@ public final class MediaSession { * The {@code sessionInfo} can include additional unchanging information about this session. * For example, it can include the version of the application, or the list of the custom * commands that this session supports. + * <p> + * Note that {@link RuntimeException} will be thrown if an app creates too many sessions. * * @param context The context to use to create the session. * @param tag A short name for debugging purposes. diff --git a/media/java/android/media/tv/TvTrackInfo.java b/media/java/android/media/tv/TvTrackInfo.java index 5e0b1eab4393..b40d43a8e616 100644 --- a/media/java/android/media/tv/TvTrackInfo.java +++ b/media/java/android/media/tv/TvTrackInfo.java @@ -353,8 +353,7 @@ public final class TvTrackInfo implements Parcelable { if (!TextUtils.equals(mId, obj.mId) || mType != obj.mType || !TextUtils.equals(mLanguage, obj.mLanguage) || !TextUtils.equals(mDescription, obj.mDescription) - || mEncrypted != obj.mEncrypted - || !Objects.equals(mExtra, obj.mExtra)) { + || mEncrypted != obj.mEncrypted) { return false; } @@ -382,7 +381,16 @@ public final class TvTrackInfo implements Parcelable { @Override public int hashCode() { - return Objects.hashCode(mId); + int result = Objects.hash(mId, mType, mLanguage, mDescription); + + if (mType == TYPE_AUDIO) { + result = Objects.hash(result, mAudioChannelCount, mAudioSampleRate); + } else if (mType == TYPE_VIDEO) { + result = Objects.hash(result, mVideoWidth, mVideoHeight, mVideoFrameRate, + mVideoPixelAspectRatio); + } + + return result; } public static final @android.annotation.NonNull Parcelable.Creator<TvTrackInfo> CREATOR = diff --git a/media/java/android/media/tv/tuner/DemuxCapabilities.java b/media/java/android/media/tv/tuner/DemuxCapabilities.java index 2c08e5bcdb19..364516cf488d 100644 --- a/media/java/android/media/tv/tuner/DemuxCapabilities.java +++ b/media/java/android/media/tv/tuner/DemuxCapabilities.java @@ -58,11 +58,13 @@ public class DemuxCapabilities { private final long mSectionFilterLength; private final int mFilterCaps; private final int[] mLinkCaps; + private final boolean mSupportTimeFilter; // Used by JNI private DemuxCapabilities(int demuxCount, int recordCount, int playbackCount, int tsFilterCount, int sectionFilterCount, int audioFilterCount, int videoFilterCount, int pesFilterCount, - int pcrFilterCount, long sectionFilterLength, int filterCaps, int[] linkCaps) { + int pcrFilterCount, long sectionFilterLength, int filterCaps, int[] linkCaps, + boolean timeFilter) { mDemuxCount = demuxCount; mRecordCount = recordCount; mPlaybackCount = playbackCount; @@ -75,6 +77,7 @@ public class DemuxCapabilities { mSectionFilterLength = sectionFilterLength; mFilterCaps = filterCaps; mLinkCaps = linkCaps; + mSupportTimeFilter = timeFilter; } /** @@ -161,4 +164,10 @@ public class DemuxCapabilities { public int[] getLinkCapabilities() { return mLinkCaps; } + /** + * Is {@link android.media.tv.tuner.filter.TimeFilter} supported. + */ + public boolean isTimeFilterSupported() { + return mSupportTimeFilter; + } } diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index bac425b57e1d..5e012447e9dd 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -105,16 +105,33 @@ public class Tuner implements AutoCloseable { * * @param tuner the Tuner instance to share frontend resource with. */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void shareFrontendFromTuner(@NonNull Tuner tuner) { // TODO: implementation. } + /** + * Updates client priority with an arbitrary value along with a nice value. + * + * <p>Tuner resource manager (TRM) uses the client priority value to decide whether it is able + * to reclaim insufficient resources from another client. + * <p>The nice value represents how much the client intends to give up the resource when an + * insufficient resource situation happens. + * + * @param priority the new priority. + * @param niceValue the nice value. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + public void updateResourcePriority(int priority, int niceValue) { + // TODO: implementation. + } private long mNativeContext; // used by native jMediaTuner /** * Releases the Tuner instance. */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) @Override public void close() { // TODO: implementation. diff --git a/media/java/android/media/tv/tuner/filter/MediaEvent.java b/media/java/android/media/tv/tuner/filter/MediaEvent.java index eb2f4a9533ad..b6bd86befd87 100644 --- a/media/java/android/media/tv/tuner/filter/MediaEvent.java +++ b/media/java/android/media/tv/tuner/filter/MediaEvent.java @@ -19,6 +19,7 @@ package android.media.tv.tuner.filter; import android.annotation.BytesLong; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.media.MediaCodec.LinearBlock; /** * Filter event sent from {@link Filter} objects with media type. @@ -32,7 +33,7 @@ public class MediaEvent extends FilterEvent{ private final long mPts; private final long mDataLength; private final long mOffset; - private final Object mLinearBuffer; + private final LinearBlock mLinearBlock; private final boolean mIsSecureMemory; private final long mDataId; private final int mMpuSequenceNumber; @@ -41,14 +42,14 @@ public class MediaEvent extends FilterEvent{ // This constructor is used by JNI code only private MediaEvent(int streamId, boolean isPtsPresent, long pts, long dataLength, long offset, - Object buffer, boolean isSecureMemory, long dataId, int mpuSequenceNumber, + LinearBlock buffer, boolean isSecureMemory, long dataId, int mpuSequenceNumber, boolean isPrivateData, AudioDescriptor extraMetaData) { mStreamId = streamId; mIsPtsPresent = isPtsPresent; mPts = pts; mDataLength = dataLength; mOffset = offset; - mLinearBuffer = buffer; + mLinearBlock = buffer; mIsSecureMemory = isSecureMemory; mDataId = dataId; mMpuSequenceNumber = mpuSequenceNumber; @@ -96,13 +97,11 @@ public class MediaEvent extends FilterEvent{ } /** - * Gets a linear buffer associated to the memory where audio or video data stays. - * TODO: use LinearBuffer when it's ready. - * - * @hide + * Gets a linear block associated to the memory where audio or video data stays. */ - public Object getLinearBuffer() { - return mLinearBuffer; + @Nullable + public LinearBlock getLinearBlock() { + return mLinearBlock; } /** @@ -125,6 +124,21 @@ public class MediaEvent extends FilterEvent{ } /** + * Gets the audio handle. + * + * <p>Client gets audio handle from {@link MediaEvent}, and queues it to + * {@link android.media.AudioTrack} in + * {@link android.media.AudioTrack#ENCAPSULATION_MODE_HANDLE} format. + * + * @return the audio handle. + * @see android.media.AudioTrack#ENCAPSULATION_MODE_HANDLE + */ + public long getAudioHandle() { + // TODO: implement + return mDataId; + } + + /** * Gets MPU sequence number of filtered data. */ public int getMpuSequenceNumber() { diff --git a/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java b/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java index 093dc6ff7d64..466fa3ecb6e7 100644 --- a/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java +++ b/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java @@ -18,6 +18,7 @@ package android.media.tv.tuner.filter; import android.annotation.BytesLong; import android.annotation.SystemApi; +import android.media.tv.tuner.filter.RecordSettings.ScHevcIndex; /** * Filter event sent from {@link Filter} objects with MMTP type. @@ -38,6 +39,7 @@ public class MmtpRecordEvent extends FilterEvent { /** * Gets indexes which can be tagged by NAL unit group in HEVC according to ISO/IEC 23008-2. */ + @ScHevcIndex public int getScHevcIndexMask() { return mScHevcIndexMask; } diff --git a/media/java/android/media/tv/tuner/filter/SectionSettings.java b/media/java/android/media/tv/tuner/filter/SectionSettings.java index 70788a7515ca..947013840bc6 100644 --- a/media/java/android/media/tv/tuner/filter/SectionSettings.java +++ b/media/java/android/media/tv/tuner/filter/SectionSettings.java @@ -16,6 +16,7 @@ package android.media.tv.tuner.filter; +import android.annotation.NonNull; import android.annotation.SystemApi; import android.media.tv.tuner.TunerUtils; @@ -25,9 +26,81 @@ import android.media.tv.tuner.TunerUtils; * @hide */ @SystemApi -public class SectionSettings extends Settings { +public abstract class SectionSettings extends Settings { + final boolean mCrcEnabled; + final boolean mIsRepeat; + final boolean mIsRaw; - SectionSettings(int mainType) { + SectionSettings(int mainType, boolean crcEnabled, boolean isRepeat, boolean isRaw) { super(TunerUtils.getFilterSubtype(mainType, Filter.SUBTYPE_SECTION)); + mCrcEnabled = crcEnabled; + mIsRepeat = isRepeat; + mIsRaw = isRaw; + } + + /** + * Returns whether the filter enables CRC (Cyclic redundancy check) and discards data which + * doesn't pass the check. + */ + public boolean isCrcEnabled() { + return mCrcEnabled; + } + /** + * Returns whether the filter repeats the data with the same version. + */ + public boolean isRepeat() { + return mIsRepeat; + } + /** + * Returns whether the filter sends {@link FilterCallback#onFilterStatusChanged} instead of + * {@link FilterCallback#onFilterEvent}. + */ + public boolean isRaw() { + return mIsRaw; + } + + /** + * Builder for {@link SectionSettings}. + * + * @param <T> The subclass to be built. + */ + public abstract static class Builder<T extends Builder<T>> + extends Settings.Builder<Builder<T>> { + boolean mCrcEnabled; + boolean mIsRepeat; + boolean mIsRaw; + + Builder(int mainType) { + super(mainType); + } + + /** + * Sets whether the filter enables CRC (Cyclic redundancy check) and discards data which + * doesn't pass the check. + */ + @NonNull + public T setCrcEnabled(boolean crcEnabled) { + mCrcEnabled = crcEnabled; + return self(); + } + /** + * Sets whether the filter repeats the data with the same version. + */ + @NonNull + public T setRepeat(boolean isRepeat) { + mIsRepeat = isRepeat; + return self(); + } + /** + * Sets whether the filter send onFilterStatus instead of + * {@link FilterCallback#onFilterEvent}. + */ + @NonNull + public T setRaw(boolean isRaw) { + mIsRaw = isRaw; + return self(); + } + + /* package */ abstract T self(); } } diff --git a/media/java/android/media/tv/tuner/filter/SectionSettingsWithSectionBits.java b/media/java/android/media/tv/tuner/filter/SectionSettingsWithSectionBits.java index eeeabdfecc01..cb547ec8ae9a 100644 --- a/media/java/android/media/tv/tuner/filter/SectionSettingsWithSectionBits.java +++ b/media/java/android/media/tv/tuner/filter/SectionSettingsWithSectionBits.java @@ -34,8 +34,9 @@ public class SectionSettingsWithSectionBits extends SectionSettings { private final byte[] mMode; - private SectionSettingsWithSectionBits(int mainType, byte[] filter, byte[] mask, byte[] mode) { - super(mainType); + private SectionSettingsWithSectionBits(int mainType, boolean isCheckCrc, boolean isRepeat, + boolean isRaw, byte[] filter, byte[] mask, byte[] mode) { + super(mainType, isCheckCrc, isRepeat, isRaw); mFilter = filter; mMask = mask; mMode = mode; @@ -86,7 +87,7 @@ public class SectionSettingsWithSectionBits extends SectionSettings { /** * Builder for {@link SectionSettingsWithSectionBits}. */ - public static class Builder extends Settings.Builder<Builder> { + public static class Builder extends SectionSettings.Builder<Builder> { private byte[] mFilter; private byte[] mMask; private byte[] mMode; @@ -125,7 +126,8 @@ public class SectionSettingsWithSectionBits extends SectionSettings { */ @NonNull public SectionSettingsWithSectionBits build() { - return new SectionSettingsWithSectionBits(mMainType, mFilter, mMask, mMode); + return new SectionSettingsWithSectionBits( + mMainType, mCrcEnabled, mIsRepeat, mIsRaw, mFilter, mMask, mMode); } @Override diff --git a/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java b/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java index c5ff45cfa7de..09d1dae971b7 100644 --- a/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java +++ b/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java @@ -32,8 +32,9 @@ public class SectionSettingsWithTableInfo extends SectionSettings { private final int mTableId; private final int mVersion; - private SectionSettingsWithTableInfo(int mainType, int tableId, int version) { - super(mainType); + private SectionSettingsWithTableInfo(int mainType, boolean isCheckCrc, boolean isRepeat, + boolean isRaw, int tableId, int version) { + super(mainType, isCheckCrc, isRepeat, isRaw); mTableId = tableId; mVersion = version; } @@ -67,7 +68,7 @@ public class SectionSettingsWithTableInfo extends SectionSettings { /** * Builder for {@link SectionSettingsWithTableInfo}. */ - public static class Builder extends Settings.Builder<Builder> { + public static class Builder extends SectionSettings.Builder<Builder> { private int mTableId; private int mVersion; @@ -97,7 +98,8 @@ public class SectionSettingsWithTableInfo extends SectionSettings { */ @NonNull public SectionSettingsWithTableInfo build() { - return new SectionSettingsWithTableInfo(mMainType, mTableId, mVersion); + return new SectionSettingsWithTableInfo( + mMainType, mCrcEnabled, mIsRepeat, mIsRaw, mTableId, mVersion); } @Override diff --git a/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java index b6878e6a13a8..3d83a74a500c 100644 --- a/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java +++ b/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java @@ -106,7 +106,7 @@ public class TlvFilterConfiguration extends FilterConfiguration { * Sets whether the data is compressed IP packet. */ @NonNull - public Builder setIsCompressedIpPacket(boolean isCompressedIpPacket) { + public Builder setCompressedIpPacket(boolean isCompressedIpPacket) { mIsCompressedIpPacket = isCompressedIpPacket; return this; } diff --git a/media/java/android/media/voice/KeyphraseModelManager.java b/media/java/android/media/voice/KeyphraseModelManager.java new file mode 100644 index 000000000000..3fa38e0a5854 --- /dev/null +++ b/media/java/android/media/voice/KeyphraseModelManager.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.voice; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.hardware.soundtrigger.SoundTrigger; +import android.os.RemoteException; +import android.os.ServiceSpecificException; +import android.util.Slog; + +import com.android.internal.app.IVoiceInteractionManagerService; + +import java.util.Locale; +import java.util.Objects; + +/** + * This class provides management of voice based sound recognition models. Usage of this class is + * restricted to system or signature applications only. This allows OEMs to write apps that can + * manage voice based sound trigger models. + * Callers of this class are expected to have whitelist manifest permission MANAGE_VOICE_KEYPHRASES. + * Callers of this class are expected to be the designated voice interaction service via + * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}. + * @hide + */ +@SystemApi +public final class KeyphraseModelManager { + private static final boolean DBG = false; + private static final String TAG = "KeyphraseModelManager"; + + private final IVoiceInteractionManagerService mVoiceInteractionManagerService; + + /** + * @hide + */ + public KeyphraseModelManager( + IVoiceInteractionManagerService voiceInteractionManagerService) { + if (DBG) { + Slog.i(TAG, "KeyphraseModelManager created."); + } + mVoiceInteractionManagerService = voiceInteractionManagerService; + } + + + /** + * Gets the registered sound model for keyphrase detection for the current user. + * The keyphraseId and locale passed must match a supported model passed in via + * {@link #updateKeyphraseSoundModel}. + * If the active voice interaction service changes from the current user, all requests will be + * rejected, and any registered models will be unregistered. + * + * @param keyphraseId The unique identifier for the keyphrase. + * @param locale The locale language tag supported by the desired model. + * @return Registered keyphrase sound model matching the keyphrase ID and locale. May be null if + * no matching sound model exists. + * @throws SecurityException Thrown when caller does not have MANAGE_VOICE_KEYPHRASES permission + * or if the caller is not the active voice interaction service. + */ + @RequiresPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES) + @Nullable + public SoundTrigger.KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, + @NonNull Locale locale) { + Objects.requireNonNull(locale); + try { + return mVoiceInteractionManagerService.getKeyphraseSoundModel(keyphraseId, + locale.toLanguageTag()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Add or update the given keyphrase sound model to the registered models pool for the current + * user. + * If a model exists with the same Keyphrase ID, locale, and user list. The registered model + * will be overwritten with the new model. + * If the active voice interaction service changes from the current user, all requests will be + * rejected, and any registered models will be unregistered. + * + * @param model Keyphrase sound model to be updated. + * @throws ServiceSpecificException Thrown with error code if failed to update the keyphrase + * sound model. + * @throws SecurityException Thrown when caller does not have MANAGE_VOICE_KEYPHRASES permission + * or if the caller is not the active voice interaction service. + */ + @RequiresPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES) + public void updateKeyphraseSoundModel(@NonNull SoundTrigger.KeyphraseSoundModel model) { + Objects.requireNonNull(model); + try { + int status = mVoiceInteractionManagerService.updateKeyphraseSoundModel(model); + if (status != SoundTrigger.STATUS_OK) { + throw new ServiceSpecificException(status); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Delete keyphrase sound model from the registered models pool for the current user matching\ + * the keyphrase ID and locale. + * The keyphraseId and locale passed must match a supported model passed in via + * {@link #updateKeyphraseSoundModel}. + * If the active voice interaction service changes from the current user, all requests will be + * rejected, and any registered models will be unregistered. + * + * @param keyphraseId The unique identifier for the keyphrase. + * @param locale The locale language tag supported by the desired model. + * @throws ServiceSpecificException Thrown with error code if failed to delete the keyphrase + * sound model. + * @throws SecurityException Thrown when caller does not have MANAGE_VOICE_KEYPHRASES permission + * or if the caller is not the active voice interaction service. + */ + @RequiresPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES) + public void deleteKeyphraseSoundModel(int keyphraseId, @NonNull Locale locale) { + Objects.requireNonNull(locale); + try { + int status = mVoiceInteractionManagerService.deleteKeyphraseSoundModel(keyphraseId, + locale.toLanguageTag()); + if (status != SoundTrigger.STATUS_OK) { + throw new ServiceSpecificException(status); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/media/jni/Android.bp b/media/jni/Android.bp index aeacd8f63cb0..c17b1b773bd5 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -1,6 +1,8 @@ cc_library_shared { name: "libmedia_jni", + defaults: ["libcodec2-internal-defaults"], + srcs: [ "android_media_ImageWriter.cpp", "android_media_ImageReader.cpp", diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index 20980a90e9fe..ab6966d5d1c3 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -18,6 +18,8 @@ #define LOG_TAG "MediaCodec-JNI" #include <utils/Log.h> +#include <type_traits> + #include "android_media_MediaCodec.h" #include "android_media_MediaCrypto.h" @@ -31,13 +33,20 @@ #include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedLocalRef.h> +#include <C2Buffer.h> + #include <android/hardware/cas/native/1.0/IDescrambler.h> +#include <binder/MemoryHeapBase.h> + #include <cutils/compiler.h> #include <gui/Surface.h> +#include <hidlmemory/FrameworkUtils.h> + #include <media/MediaCodecBuffer.h> +#include <media/hardware/VideoAPI.h> #include <media/stagefright/MediaCodec.h> #include <media/stagefright/foundation/ABuffer.h> #include <media/stagefright/foundation/ADebug.h> @@ -47,7 +56,6 @@ #include <media/stagefright/MediaErrors.h> #include <media/stagefright/PersistentSurface.h> #include <mediadrm/ICrypto.h> -#include <nativehelper/ScopedLocalRef.h> #include <system/window.h> @@ -110,6 +118,39 @@ static struct { jfieldID levelField; } gCodecInfo; +static struct { + jclass clazz; + jobject nativeByteOrder; + jmethodID orderId; + jmethodID asReadOnlyBufferId; + jmethodID positionId; + jmethodID limitId; +} gByteBufferInfo; + +static struct { + jmethodID sizeId; + jmethodID getId; + jmethodID addId; +} gArrayListInfo; + +static struct { + jclass clazz; + jmethodID ctorId; + jmethodID setInternalStateId; + jfieldID contextId; + jfieldID validId; + jfieldID lockId; +} gLinearBlockInfo; + +static struct { + jclass clazz; + jmethodID ctorId; + jmethodID setInternalStateId; + jfieldID contextId; + jfieldID validId; + jfieldID lockId; +} gGraphicBlockInfo; + struct fields_t { jmethodID postEventFromNativeID; jmethodID lockAndGetContextID; @@ -123,11 +164,65 @@ struct fields_t { jfieldID cryptoInfoPatternID; jfieldID patternEncryptBlocksID; jfieldID patternSkipBlocksID; + jfieldID queueRequestIndexID; + jfieldID outputFrameLinearBlockID; + jfieldID outputFrameGraphicBlockID; + jfieldID outputFrameChangedKeysID; + jfieldID outputFrameFormatID; }; static fields_t gFields; static const void *sRefBaseOwner; +struct JMediaCodecLinearBlock { + std::shared_ptr<C2Buffer> mBuffer; + std::shared_ptr<C2ReadView> mReadonlyMapping; + + std::shared_ptr<C2LinearBlock> mBlock; + std::shared_ptr<C2WriteView> mReadWriteMapping; + + sp<IMemoryHeap> mHeap; + sp<hardware::HidlMemory> mMemory; + + sp<MediaCodecBuffer> mLegacyBuffer; + + std::once_flag mCopyWarningFlag; + + std::shared_ptr<C2Buffer> toC2Buffer(size_t offset, size_t size) { + if (mBuffer) { + if (mBuffer->data().type() != C2BufferData::LINEAR) { + return nullptr; + } + C2ConstLinearBlock block = mBuffer->data().linearBlocks().front(); + if (offset == 0 && size == block.capacity()) { + return mBuffer; + } + return C2Buffer::CreateLinearBuffer(block.subBlock(offset, size)); + } + if (mBlock) { + return C2Buffer::CreateLinearBuffer(mBlock->share(offset, size, C2Fence{})); + } + return nullptr; + } + + sp<hardware::HidlMemory> toHidlMemory() { + if (mMemory) { + return mMemory; + } + return nullptr; + } +}; + +struct JMediaCodecGraphicBlock { + std::shared_ptr<C2Buffer> mBuffer; + std::shared_ptr<const C2GraphicView> mReadonlyMapping; + + std::shared_ptr<C2GraphicBlock> mBlock; + std::shared_ptr<C2GraphicView> mReadWriteMapping; + + sp<MediaCodecBuffer> mLegacyBuffer; +}; + //////////////////////////////////////////////////////////////////////////////// JMediaCodec::JMediaCodec( @@ -141,8 +236,6 @@ JMediaCodec::JMediaCodec( mClass = (jclass)env->NewGlobalRef(clazz); mObject = env->NewWeakGlobalRef(thiz); - cacheJavaObjects(env); - mLooper = new ALooper; mLooper->setName("MediaCodec_looper"); @@ -163,45 +256,6 @@ JMediaCodec::JMediaCodec( CHECK((mCodec != NULL) != (mInitStatus != OK)); } -void JMediaCodec::cacheJavaObjects(JNIEnv *env) { - jclass clazz = (jclass)env->FindClass("java/nio/ByteBuffer"); - mByteBufferClass = (jclass)env->NewGlobalRef(clazz); - CHECK(mByteBufferClass != NULL); - - ScopedLocalRef<jclass> byteOrderClass( - env, env->FindClass("java/nio/ByteOrder")); - CHECK(byteOrderClass.get() != NULL); - - jmethodID nativeOrderID = env->GetStaticMethodID( - byteOrderClass.get(), "nativeOrder", "()Ljava/nio/ByteOrder;"); - CHECK(nativeOrderID != NULL); - - jobject nativeByteOrderObj = - env->CallStaticObjectMethod(byteOrderClass.get(), nativeOrderID); - mNativeByteOrderObj = env->NewGlobalRef(nativeByteOrderObj); - CHECK(mNativeByteOrderObj != NULL); - env->DeleteLocalRef(nativeByteOrderObj); - nativeByteOrderObj = NULL; - - mByteBufferOrderMethodID = env->GetMethodID( - mByteBufferClass, - "order", - "(Ljava/nio/ByteOrder;)Ljava/nio/ByteBuffer;"); - CHECK(mByteBufferOrderMethodID != NULL); - - mByteBufferAsReadOnlyBufferMethodID = env->GetMethodID( - mByteBufferClass, "asReadOnlyBuffer", "()Ljava/nio/ByteBuffer;"); - CHECK(mByteBufferAsReadOnlyBufferMethodID != NULL); - - mByteBufferPositionMethodID = env->GetMethodID( - mByteBufferClass, "position", "(I)Ljava/nio/Buffer;"); - CHECK(mByteBufferPositionMethodID != NULL); - - mByteBufferLimitMethodID = env->GetMethodID( - mByteBufferClass, "limit", "(I)Ljava/nio/Buffer;"); - CHECK(mByteBufferLimitMethodID != NULL); -} - status_t JMediaCodec::initCheck() const { return mInitStatus; } @@ -247,19 +301,6 @@ JMediaCodec::~JMediaCodec() { mObject = NULL; env->DeleteGlobalRef(mClass); mClass = NULL; - deleteJavaObjects(env); -} - -void JMediaCodec::deleteJavaObjects(JNIEnv *env) { - env->DeleteGlobalRef(mByteBufferClass); - mByteBufferClass = NULL; - env->DeleteGlobalRef(mNativeByteOrderObj); - mNativeByteOrderObj = NULL; - - mByteBufferOrderMethodID = NULL; - mByteBufferAsReadOnlyBufferMethodID = NULL; - mByteBufferPositionMethodID = NULL; - mByteBufferLimitMethodID = NULL; } status_t JMediaCodec::enableOnFrameRenderedListener(jboolean enable) { @@ -300,6 +341,12 @@ status_t JMediaCodec::configure( mSurfaceTextureClient.clear(); } + constexpr int32_t CONFIGURE_FLAG_ENCODE = 1; + AString mime; + CHECK(format->findString("mime", &mime)); + mGraphicOutput = (mime.startsWithIgnoreCase("video/") || mime.startsWithIgnoreCase("image/")) + && !(flags & CONFIGURE_FLAG_ENCODE); + return mCodec->configure( format, mSurfaceTextureClient, crypto, descrambler, flags); } @@ -370,6 +417,32 @@ status_t JMediaCodec::queueSecureInputBuffer( presentationTimeUs, flags, errorDetailMsg); } +status_t JMediaCodec::queueBuffer( + size_t index, const std::shared_ptr<C2Buffer> &buffer, int64_t timeUs, + uint32_t flags, const sp<AMessage> &tunings, AString *errorDetailMsg) { + return mCodec->queueBuffer( + index, buffer, timeUs, flags, tunings, errorDetailMsg); +} + +status_t JMediaCodec::queueEncryptedLinearBlock( + size_t index, + const sp<hardware::HidlMemory> &buffer, + size_t offset, + const CryptoPlugin::SubSample *subSamples, + size_t numSubSamples, + const uint8_t key[16], + const uint8_t iv[16], + CryptoPlugin::Mode mode, + const CryptoPlugin::Pattern &pattern, + int64_t presentationTimeUs, + uint32_t flags, + const sp<AMessage> &tunings, + AString *errorDetailMsg) { + return mCodec->queueEncryptedBuffer( + index, buffer, offset, subSamples, numSubSamples, key, iv, mode, pattern, + presentationTimeUs, flags, tunings, errorDetailMsg); +} + status_t JMediaCodec::dequeueInputBuffer(size_t *index, int64_t timeoutUs) { return mCodec->dequeueInputBuffer(index, timeoutUs); } @@ -444,7 +517,7 @@ status_t JMediaCodec::getBuffers( } *bufArray = (jobjectArray)env->NewObjectArray( - buffers.size(), mByteBufferClass, NULL); + buffers.size(), gByteBufferInfo.clazz, NULL); if (*bufArray == NULL) { return NO_MEMORY; } @@ -470,6 +543,39 @@ status_t JMediaCodec::getBuffers( return OK; } +template <typename T> +static jobject CreateByteBuffer( + JNIEnv *env, T *base, size_t capacity, size_t offset, size_t size, + bool readOnly, bool clearBuffer) { + jobject byteBuffer = + env->NewDirectByteBuffer( + const_cast<typename std::remove_const<T>::type *>(base), + capacity); + if (readOnly && byteBuffer != NULL) { + jobject readOnlyBuffer = env->CallObjectMethod( + byteBuffer, gByteBufferInfo.asReadOnlyBufferId); + env->DeleteLocalRef(byteBuffer); + byteBuffer = readOnlyBuffer; + } + if (byteBuffer == NULL) { + return nullptr; + } + jobject me = env->CallObjectMethod( + byteBuffer, gByteBufferInfo.orderId, gByteBufferInfo.nativeByteOrder); + env->DeleteLocalRef(me); + me = env->CallObjectMethod( + byteBuffer, gByteBufferInfo.limitId, + clearBuffer ? capacity : offset + size); + env->DeleteLocalRef(me); + me = env->CallObjectMethod( + byteBuffer, gByteBufferInfo.positionId, + clearBuffer ? 0 : offset); + env->DeleteLocalRef(me); + me = NULL; + return byteBuffer; +} + + // static template <typename T> status_t JMediaCodec::createByteBufferFromABuffer( @@ -488,29 +594,9 @@ status_t JMediaCodec::createByteBufferFromABuffer( return OK; } - jobject byteBuffer = - env->NewDirectByteBuffer(buffer->base(), buffer->capacity()); - if (readOnly && byteBuffer != NULL) { - jobject readOnlyBuffer = env->CallObjectMethod( - byteBuffer, mByteBufferAsReadOnlyBufferMethodID); - env->DeleteLocalRef(byteBuffer); - byteBuffer = readOnlyBuffer; - } - if (byteBuffer == NULL) { - return NO_MEMORY; - } - jobject me = env->CallObjectMethod( - byteBuffer, mByteBufferOrderMethodID, mNativeByteOrderObj); - env->DeleteLocalRef(me); - me = env->CallObjectMethod( - byteBuffer, mByteBufferLimitMethodID, - clearBuffer ? buffer->capacity() : (buffer->offset() + buffer->size())); - env->DeleteLocalRef(me); - me = env->CallObjectMethod( - byteBuffer, mByteBufferPositionMethodID, - clearBuffer ? 0 : buffer->offset()); - env->DeleteLocalRef(me); - me = NULL; + jobject byteBuffer = CreateByteBuffer( + env, buffer->base(), buffer->capacity(), buffer->offset(), buffer->size(), + readOnly, clearBuffer); *buf = byteBuffer; return OK; @@ -628,6 +714,92 @@ status_t JMediaCodec::getImage( return OK; } +status_t JMediaCodec::getOutputFrame( + JNIEnv *env, jobject frame, size_t index) const { + sp<MediaCodecBuffer> buffer; + + status_t err = mCodec->getOutputBuffer(index, &buffer); + if (err != OK) { + return err; + } + + if (buffer->size() > 0) { + // asC2Buffer clears internal reference, so set the reference again. + std::shared_ptr<C2Buffer> c2Buffer = buffer->asC2Buffer(); + buffer->copy(c2Buffer); + if (c2Buffer) { + switch (c2Buffer->data().type()) { + case C2BufferData::LINEAR: { + std::unique_ptr<JMediaCodecLinearBlock> context{new JMediaCodecLinearBlock}; + context->mBuffer = c2Buffer; + ScopedLocalRef<jobject> linearBlock{env, env->NewObject( + gLinearBlockInfo.clazz, gLinearBlockInfo.ctorId)}; + env->SetLongField( + linearBlock.get(), gLinearBlockInfo.contextId, (jlong)context.release()); + env->SetObjectField(frame, gFields.outputFrameLinearBlockID, linearBlock.get()); + break; + } + case C2BufferData::GRAPHIC: { + std::unique_ptr<JMediaCodecGraphicBlock> context{new JMediaCodecGraphicBlock}; + context->mBuffer = c2Buffer; + ScopedLocalRef<jobject> graphicBlock{env, env->NewObject( + gGraphicBlockInfo.clazz, gGraphicBlockInfo.ctorId)}; + env->SetLongField( + graphicBlock.get(), gGraphicBlockInfo.contextId, (jlong)context.release()); + env->SetObjectField(frame, gFields.outputFrameGraphicBlockID, graphicBlock.get()); + break; + } + case C2BufferData::LINEAR_CHUNKS: [[fallthrough]]; + case C2BufferData::GRAPHIC_CHUNKS: [[fallthrough]]; + case C2BufferData::INVALID: [[fallthrough]]; + default: + return INVALID_OPERATION; + } + } else { + if (!mGraphicOutput) { + std::unique_ptr<JMediaCodecLinearBlock> context{new JMediaCodecLinearBlock}; + context->mLegacyBuffer = buffer; + ScopedLocalRef<jobject> linearBlock{env, env->NewObject( + gLinearBlockInfo.clazz, gLinearBlockInfo.ctorId)}; + env->SetLongField( + linearBlock.get(), gLinearBlockInfo.contextId, (jlong)context.release()); + env->SetObjectField(frame, gFields.outputFrameLinearBlockID, linearBlock.get()); + } else { + std::unique_ptr<JMediaCodecGraphicBlock> context{new JMediaCodecGraphicBlock}; + context->mLegacyBuffer = buffer; + ScopedLocalRef<jobject> graphicBlock{env, env->NewObject( + gGraphicBlockInfo.clazz, gGraphicBlockInfo.ctorId)}; + env->SetLongField( + graphicBlock.get(), gGraphicBlockInfo.contextId, (jlong)context.release()); + env->SetObjectField(frame, gFields.outputFrameGraphicBlockID, graphicBlock.get()); + } + } + } + + jobject format; + err = getOutputFormat(env, index, &format); + if (err != OK) { + return err; + } + env->SetObjectField(frame, gFields.outputFrameFormatID, format); + env->DeleteLocalRef(format); + format = nullptr; + + sp<RefBase> obj; + if (buffer->meta()->findObject("changedKeys", &obj) && obj) { + sp<MediaCodec::WrapperObject<std::set<std::string>>> changedKeys{ + (decltype(changedKeys.get()))obj.get()}; + ScopedLocalRef<jobject> changedKeysObj{env, env->GetObjectField( + frame, gFields.outputFrameChangedKeysID)}; + for (const std::string &key : changedKeys->value) { + ScopedLocalRef<jstring> keyStr{env, env->NewStringUTF(key.c_str())}; + (void)env->CallBooleanMethod(changedKeysObj.get(), gArrayListInfo.addId, keyStr.get()); + } + } + return OK; +} + + status_t JMediaCodec::getName(JNIEnv *env, jstring *nameStr) const { AString name; @@ -1428,6 +1600,139 @@ static void android_media_MediaCodec_queueInputBuffer( env, err, ACTION_CODE_FATAL, errorDetailMsg.empty() ? NULL : errorDetailMsg.c_str()); } +struct NativeCryptoInfo { + NativeCryptoInfo(JNIEnv *env, jobject cryptoInfoObj) + : mEnv{env}, + mIvObj{env, (jbyteArray)env->GetObjectField(cryptoInfoObj, gFields.cryptoInfoIVID)}, + mKeyObj{env, (jbyteArray)env->GetObjectField(cryptoInfoObj, gFields.cryptoInfoKeyID)} { + mNumSubSamples = env->GetIntField(cryptoInfoObj, gFields.cryptoInfoNumSubSamplesID); + + ScopedLocalRef<jintArray> numBytesOfClearDataObj{env, (jintArray)env->GetObjectField( + cryptoInfoObj, gFields.cryptoInfoNumBytesOfClearDataID)}; + + ScopedLocalRef<jintArray> numBytesOfEncryptedDataObj{env, (jintArray)env->GetObjectField( + cryptoInfoObj, gFields.cryptoInfoNumBytesOfEncryptedDataID)}; + + jint jmode = env->GetIntField(cryptoInfoObj, gFields.cryptoInfoModeID); + if (jmode == gCryptoModes.Unencrypted) { + mMode = CryptoPlugin::kMode_Unencrypted; + } else if (jmode == gCryptoModes.AesCtr) { + mMode = CryptoPlugin::kMode_AES_CTR; + } else if (jmode == gCryptoModes.AesCbc) { + mMode = CryptoPlugin::kMode_AES_CBC; + } else { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return; + } + + ScopedLocalRef<jobject> patternObj{ + env, env->GetObjectField(cryptoInfoObj, gFields.cryptoInfoPatternID)}; + + CryptoPlugin::Pattern pattern; + if (patternObj.get() == nullptr) { + pattern.mEncryptBlocks = 0; + pattern.mSkipBlocks = 0; + } else { + pattern.mEncryptBlocks = env->GetIntField( + patternObj.get(), gFields.patternEncryptBlocksID); + pattern.mSkipBlocks = env->GetIntField( + patternObj.get(), gFields.patternSkipBlocksID); + } + + mErr = OK; + if (mNumSubSamples <= 0) { + mErr = -EINVAL; + } else if (numBytesOfClearDataObj == nullptr + && numBytesOfEncryptedDataObj == nullptr) { + mErr = -EINVAL; + } else if (numBytesOfEncryptedDataObj != nullptr + && env->GetArrayLength(numBytesOfEncryptedDataObj.get()) < mNumSubSamples) { + mErr = -ERANGE; + } else if (numBytesOfClearDataObj != nullptr + && env->GetArrayLength(numBytesOfClearDataObj.get()) < mNumSubSamples) { + mErr = -ERANGE; + // subSamples array may silently overflow if number of samples are too large. Use + // INT32_MAX as maximum allocation size may be less than SIZE_MAX on some platforms + } else if (CC_UNLIKELY(mNumSubSamples >= (signed)(INT32_MAX / sizeof(*mSubSamples))) ) { + mErr = -EINVAL; + } else { + jint *numBytesOfClearData = + (numBytesOfClearDataObj == nullptr) + ? nullptr + : env->GetIntArrayElements(numBytesOfClearDataObj.get(), nullptr); + + jint *numBytesOfEncryptedData = + (numBytesOfEncryptedDataObj == nullptr) + ? nullptr + : env->GetIntArrayElements(numBytesOfEncryptedDataObj.get(), nullptr); + + mSubSamples = new CryptoPlugin::SubSample[mNumSubSamples]; + + for (jint i = 0; i < mNumSubSamples; ++i) { + mSubSamples[i].mNumBytesOfClearData = + (numBytesOfClearData == nullptr) ? 0 : numBytesOfClearData[i]; + + mSubSamples[i].mNumBytesOfEncryptedData = + (numBytesOfEncryptedData == nullptr) ? 0 : numBytesOfEncryptedData[i]; + } + + if (numBytesOfEncryptedData != nullptr) { + env->ReleaseIntArrayElements( + numBytesOfEncryptedDataObj.get(), numBytesOfEncryptedData, 0); + numBytesOfEncryptedData = nullptr; + } + + if (numBytesOfClearData != nullptr) { + env->ReleaseIntArrayElements( + numBytesOfClearDataObj.get(), numBytesOfClearData, 0); + numBytesOfClearData = nullptr; + } + } + + if (mErr == OK && mKeyObj.get() != nullptr) { + if (env->GetArrayLength(mKeyObj.get()) != 16) { + mErr = -EINVAL; + } else { + mKey = env->GetByteArrayElements(mKeyObj.get(), nullptr); + } + } + + if (mErr == OK && mIvObj.get() != nullptr) { + if (env->GetArrayLength(mIvObj.get()) != 16) { + mErr = -EINVAL; + } else { + mIv = env->GetByteArrayElements(mIvObj.get(), nullptr); + } + } + } + + ~NativeCryptoInfo() { + if (mIv != nullptr) { + mEnv->ReleaseByteArrayElements(mIvObj.get(), mIv, 0); + } + + if (mKey != nullptr) { + mEnv->ReleaseByteArrayElements(mKeyObj.get(), mKey, 0); + } + + if (mSubSamples != nullptr) { + delete[] mSubSamples; + } + } + + JNIEnv *mEnv{nullptr}; + ScopedLocalRef<jbyteArray> mIvObj; + ScopedLocalRef<jbyteArray> mKeyObj; + status_t mErr{OK}; + + CryptoPlugin::SubSample *mSubSamples{nullptr}; + int32_t mNumSubSamples{0}; + jbyte *mIv{nullptr}; + jbyte *mKey{nullptr}; + enum CryptoPlugin::Mode mMode; + CryptoPlugin::Pattern mPattern; +}; + static void android_media_MediaCodec_queueSecureInputBuffer( JNIEnv *env, jobject thiz, @@ -1445,152 +1750,274 @@ static void android_media_MediaCodec_queueSecureInputBuffer( return; } - jint numSubSamples = - env->GetIntField(cryptoInfoObj, gFields.cryptoInfoNumSubSamplesID); - - jintArray numBytesOfClearDataObj = - (jintArray)env->GetObjectField( - cryptoInfoObj, gFields.cryptoInfoNumBytesOfClearDataID); + NativeCryptoInfo cryptoInfo{env, cryptoInfoObj}; + AString errorDetailMsg; - jintArray numBytesOfEncryptedDataObj = - (jintArray)env->GetObjectField( - cryptoInfoObj, gFields.cryptoInfoNumBytesOfEncryptedDataID); + status_t err = cryptoInfo.mErr; + if (err == OK) { + err = codec->queueSecureInputBuffer( + index, offset, + cryptoInfo.mSubSamples, cryptoInfo.mNumSubSamples, + (const uint8_t *)cryptoInfo.mKey, (const uint8_t *)cryptoInfo.mIv, + cryptoInfo.mMode, + cryptoInfo.mPattern, + timestampUs, + flags, + &errorDetailMsg); + } - jbyteArray keyObj = - (jbyteArray)env->GetObjectField(cryptoInfoObj, gFields.cryptoInfoKeyID); + throwExceptionAsNecessary( + env, err, ACTION_CODE_FATAL, errorDetailMsg.empty() ? NULL : errorDetailMsg.c_str()); +} - jbyteArray ivObj = - (jbyteArray)env->GetObjectField(cryptoInfoObj, gFields.cryptoInfoIVID); +static status_t ConvertKeyValueListsToAMessage( + JNIEnv *env, jobject keys, jobject values, sp<AMessage> *msg) { + static struct Fields { + explicit Fields(JNIEnv *env) { + ScopedLocalRef<jclass> clazz{env, env->FindClass("java/lang/String")}; + CHECK(clazz.get() != NULL); + mStringClass = (jclass)env->NewGlobalRef(clazz.get()); - jint jmode = env->GetIntField(cryptoInfoObj, gFields.cryptoInfoModeID); - enum CryptoPlugin::Mode mode; - if (jmode == gCryptoModes.Unencrypted) { - mode = CryptoPlugin::kMode_Unencrypted; - } else if (jmode == gCryptoModes.AesCtr) { - mode = CryptoPlugin::kMode_AES_CTR; - } else if (jmode == gCryptoModes.AesCbc) { - mode = CryptoPlugin::kMode_AES_CBC; - } else { - throwExceptionAsNecessary(env, INVALID_OPERATION); - return; - } + clazz.reset(env->FindClass("java/lang/Integer")); + CHECK(clazz.get() != NULL); + mIntegerClass = (jclass)env->NewGlobalRef(clazz.get()); - jobject patternObj = env->GetObjectField(cryptoInfoObj, gFields.cryptoInfoPatternID); + mIntegerValueId = env->GetMethodID(clazz.get(), "intValue", "()I"); + CHECK(mIntegerValueId != NULL); - CryptoPlugin::Pattern pattern; - if (patternObj == NULL) { - pattern.mEncryptBlocks = 0; - pattern.mSkipBlocks = 0; - } else { - pattern.mEncryptBlocks = env->GetIntField(patternObj, gFields.patternEncryptBlocksID); - pattern.mSkipBlocks = env->GetIntField(patternObj, gFields.patternSkipBlocksID); - } - - status_t err = OK; - - CryptoPlugin::SubSample *subSamples = NULL; - jbyte *key = NULL; - jbyte *iv = NULL; - - if (numSubSamples <= 0) { - err = -EINVAL; - } else if (numBytesOfClearDataObj == NULL - && numBytesOfEncryptedDataObj == NULL) { - err = -EINVAL; - } else if (numBytesOfEncryptedDataObj != NULL - && env->GetArrayLength(numBytesOfEncryptedDataObj) < numSubSamples) { - err = -ERANGE; - } else if (numBytesOfClearDataObj != NULL - && env->GetArrayLength(numBytesOfClearDataObj) < numSubSamples) { - err = -ERANGE; - // subSamples array may silently overflow if number of samples are too large. Use - // INT32_MAX as maximum allocation size may be less than SIZE_MAX on some platforms - } else if ( CC_UNLIKELY(numSubSamples >= (signed)(INT32_MAX / sizeof(*subSamples))) ) { - err = -EINVAL; - } else { - jboolean isCopy; + clazz.reset(env->FindClass("java/lang/Long")); + CHECK(clazz.get() != NULL); + mLongClass = (jclass)env->NewGlobalRef(clazz.get()); - jint *numBytesOfClearData = - (numBytesOfClearDataObj == NULL) - ? NULL - : env->GetIntArrayElements(numBytesOfClearDataObj, &isCopy); + mLongValueId = env->GetMethodID(clazz.get(), "longValue", "()J"); + CHECK(mLongValueId != NULL); - jint *numBytesOfEncryptedData = - (numBytesOfEncryptedDataObj == NULL) - ? NULL - : env->GetIntArrayElements(numBytesOfEncryptedDataObj, &isCopy); + clazz.reset(env->FindClass("java/lang/Float")); + CHECK(clazz.get() != NULL); + mFloatClass = (jclass)env->NewGlobalRef(clazz.get()); - subSamples = new CryptoPlugin::SubSample[numSubSamples]; + mFloatValueId = env->GetMethodID(clazz.get(), "floatValue", "()F"); + CHECK(mFloatValueId != NULL); - for (jint i = 0; i < numSubSamples; ++i) { - subSamples[i].mNumBytesOfClearData = - (numBytesOfClearData == NULL) ? 0 : numBytesOfClearData[i]; + clazz.reset(env->FindClass("java/util/ArrayList")); + CHECK(clazz.get() != NULL); - subSamples[i].mNumBytesOfEncryptedData = - (numBytesOfEncryptedData == NULL) - ? 0 : numBytesOfEncryptedData[i]; + mByteBufferArrayId = env->GetMethodID(gByteBufferInfo.clazz, "array", "()[B"); + CHECK(mByteBufferArrayId != NULL); } - if (numBytesOfEncryptedData != NULL) { - env->ReleaseIntArrayElements( - numBytesOfEncryptedDataObj, numBytesOfEncryptedData, 0); - numBytesOfEncryptedData = NULL; + jclass mStringClass; + jclass mIntegerClass; + jmethodID mIntegerValueId; + jclass mLongClass; + jmethodID mLongValueId; + jclass mFloatClass; + jmethodID mFloatValueId; + jmethodID mByteBufferArrayId; + } sFields{env}; + + jint size = env->CallIntMethod(keys, gArrayListInfo.sizeId); + if (size != env->CallIntMethod(values, gArrayListInfo.sizeId)) { + return BAD_VALUE; + } + + sp<AMessage> result{new AMessage}; + for (jint i = 0; i < size; ++i) { + ScopedLocalRef<jstring> jkey{ + env, (jstring)env->CallObjectMethod(keys, gArrayListInfo.getId, i)}; + const char *tmp = env->GetStringUTFChars(jkey.get(), nullptr); + AString key; + if (tmp) { + key.setTo(tmp); + } + env->ReleaseStringUTFChars(jkey.get(), tmp); + if (key.empty()) { + return NO_MEMORY; } - if (numBytesOfClearData != NULL) { - env->ReleaseIntArrayElements( - numBytesOfClearDataObj, numBytesOfClearData, 0); - numBytesOfClearData = NULL; + ScopedLocalRef<jobject> jvalue{ + env, env->CallObjectMethod(values, gArrayListInfo.getId, i)}; + + if (env->IsInstanceOf(jvalue.get(), sFields.mStringClass)) { + const char *tmp = env->GetStringUTFChars((jstring)jvalue.get(), nullptr); + AString value; + if (tmp) { + value.setTo(tmp); + } + env->ReleaseStringUTFChars((jstring)jvalue.get(), tmp); + if (value.empty()) { + return NO_MEMORY; + } + result->setString(key.c_str(), value); + } else if (env->IsInstanceOf(jvalue.get(), sFields.mIntegerClass)) { + jint value = env->CallIntMethod(jvalue.get(), sFields.mIntegerValueId); + result->setInt32(key.c_str(), value); + } else if (env->IsInstanceOf(jvalue.get(), sFields.mLongClass)) { + jlong value = env->CallLongMethod(jvalue.get(), sFields.mLongValueId); + result->setInt64(key.c_str(), value); + } else if (env->IsInstanceOf(jvalue.get(), sFields.mFloatClass)) { + jfloat value = env->CallFloatMethod(jvalue.get(), sFields.mFloatValueId); + result->setFloat(key.c_str(), value); + } else if (env->IsInstanceOf(jvalue.get(), gByteBufferInfo.clazz)) { + jint position = env->CallIntMethod(jvalue.get(), gByteBufferInfo.positionId); + jint limit = env->CallIntMethod(jvalue.get(), gByteBufferInfo.limitId); + sp<ABuffer> buffer{new ABuffer(limit - position)}; + void *data = env->GetDirectBufferAddress(jvalue.get()); + if (data != nullptr) { + memcpy(buffer->data(), + static_cast<const uint8_t *>(data) + position, + buffer->size()); + } else { + ScopedLocalRef<jbyteArray> byteArray{env, (jbyteArray)env->CallObjectMethod( + jvalue.get(), sFields.mByteBufferArrayId)}; + env->GetByteArrayRegion(byteArray.get(), position, buffer->size(), + reinterpret_cast<jbyte *>(buffer->data())); + } + result->setBuffer(key.c_str(), buffer); } } - if (err == OK && keyObj != NULL) { - if (env->GetArrayLength(keyObj) != 16) { - err = -EINVAL; - } else { - jboolean isCopy; - key = env->GetByteArrayElements(keyObj, &isCopy); - } + *msg = result; + return OK; +} + +static void android_media_MediaCodec_native_queueLinearBlock( + JNIEnv *env, jobject thiz, jint index, jobject bufferObj, + jint offset, jint size, jobject cryptoInfoObj, + jlong presentationTimeUs, jint flags, jobject keys, jobject values) { + ALOGV("android_media_MediaCodec_native_queueLinearBlock"); + + sp<JMediaCodec> codec = getMediaCodec(env, thiz); + + if (codec == nullptr) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return; } - if (err == OK && ivObj != NULL) { - if (env->GetArrayLength(ivObj) != 16) { - err = -EINVAL; - } else { - jboolean isCopy; - iv = env->GetByteArrayElements(ivObj, &isCopy); + sp<AMessage> tunings; + status_t err = ConvertKeyValueListsToAMessage(env, keys, values, &tunings); + if (err != OK) { + throwExceptionAsNecessary(env, err); + return; + } + + std::shared_ptr<C2Buffer> buffer; + sp<hardware::HidlMemory> memory; + ScopedLocalRef<jobject> lock{env, env->GetObjectField(bufferObj, gLinearBlockInfo.lockId)}; + if (env->MonitorEnter(lock.get()) == JNI_OK) { + if (env->GetBooleanField(bufferObj, gLinearBlockInfo.validId)) { + JMediaCodecLinearBlock *context = + (JMediaCodecLinearBlock *)env->GetLongField(bufferObj, gLinearBlockInfo.contextId); + if (cryptoInfoObj != nullptr) { + memory = context->toHidlMemory(); + } else { + buffer = context->toC2Buffer(offset, size); + } } + env->MonitorExit(lock.get()); + } else { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return; } AString errorDetailMsg; + if (cryptoInfoObj != nullptr) { + if (!memory) { + throwExceptionAsNecessary(env, BAD_VALUE); + return; + } - if (err == OK) { - err = codec->queueSecureInputBuffer( - index, offset, - subSamples, numSubSamples, - (const uint8_t *)key, (const uint8_t *)iv, - mode, - pattern, - timestampUs, + NativeCryptoInfo cryptoInfo{env, cryptoInfoObj}; + size_t cryptoSize = 0; + for (int i = 0; i < cryptoInfo.mNumSubSamples; ++i) { + cryptoSize += cryptoInfo.mSubSamples[i].mNumBytesOfClearData; + cryptoSize += cryptoInfo.mSubSamples[i].mNumBytesOfEncryptedData; + } + err = codec->queueEncryptedLinearBlock( + index, + memory, + offset, + cryptoInfo.mSubSamples, cryptoInfo.mNumSubSamples, + (const uint8_t *)cryptoInfo.mKey, (const uint8_t *)cryptoInfo.mIv, + cryptoInfo.mMode, + cryptoInfo.mPattern, + presentationTimeUs, flags, + tunings, &errorDetailMsg); + } else { + if (!buffer) { + throwExceptionAsNecessary(env, BAD_VALUE); + return; + } + err = codec->queueBuffer( + index, buffer, presentationTimeUs, flags, tunings, &errorDetailMsg); } + throwExceptionAsNecessary(env, err, ACTION_CODE_FATAL, errorDetailMsg.c_str()); +} + +static void android_media_MediaCodec_native_queueGraphicBlock( + JNIEnv *env, jobject thiz, jint index, jobject bufferObj, + jlong presentationTimeUs, jint flags, jobject keys, jobject values) { + ALOGV("android_media_MediaCodec_native_queueGraphicBlock"); + + sp<JMediaCodec> codec = getMediaCodec(env, thiz); - if (iv != NULL) { - env->ReleaseByteArrayElements(ivObj, iv, 0); - iv = NULL; + if (codec == NULL) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return; } - if (key != NULL) { - env->ReleaseByteArrayElements(keyObj, key, 0); - key = NULL; + sp<AMessage> tunings; + status_t err = ConvertKeyValueListsToAMessage(env, keys, values, &tunings); + if (err != OK) { + throwExceptionAsNecessary(env, err); + return; } - delete[] subSamples; - subSamples = NULL; + std::shared_ptr<C2Buffer> buffer; + std::shared_ptr<C2GraphicBlock> block; + ScopedLocalRef<jobject> lock{env, env->GetObjectField(bufferObj, gGraphicBlockInfo.lockId)}; + if (env->MonitorEnter(lock.get()) == JNI_OK) { + if (env->GetBooleanField(bufferObj, gGraphicBlockInfo.validId)) { + JMediaCodecGraphicBlock *context = (JMediaCodecGraphicBlock *)env->GetLongField( + bufferObj, gGraphicBlockInfo.contextId); + buffer = context->mBuffer; + block = context->mBlock; + } + env->MonitorExit(lock.get()); + } else { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return; + } - throwExceptionAsNecessary( - env, err, ACTION_CODE_FATAL, errorDetailMsg.empty() ? NULL : errorDetailMsg.c_str()); + if (!block && !buffer) { + throwExceptionAsNecessary(env, BAD_VALUE); + return; + } + if (!buffer) { + buffer = C2Buffer::CreateGraphicBuffer(block->share(block->crop(), C2Fence{})); + } + AString errorDetailMsg; + err = codec->queueBuffer(index, buffer, presentationTimeUs, flags, tunings, &errorDetailMsg); + throwExceptionAsNecessary(env, err, ACTION_CODE_FATAL, errorDetailMsg.c_str()); +} + +static void android_media_MediaCodec_native_getOutputFrame( + JNIEnv *env, jobject thiz, jobject frame, jint index) { + ALOGV("android_media_MediaCodec_native_getOutputFrame"); + + sp<JMediaCodec> codec = getMediaCodec(env, thiz); + + if (codec == NULL) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return; + } + + status_t err = codec->getOutputFrame(env, frame, index); + if (err != OK) { + throwExceptionAsNecessary(env, err); + } } static jint android_media_MediaCodec_dequeueInputBuffer( @@ -1991,6 +2418,31 @@ static void android_media_MediaCodec_native_init(JNIEnv *env) { gFields.patternSkipBlocksID = env->GetFieldID(clazz.get(), "mSkipBlocks", "I"); CHECK(gFields.patternSkipBlocksID != NULL); + clazz.reset(env->FindClass("android/media/MediaCodec$QueueRequest")); + CHECK(clazz.get() != NULL); + + gFields.queueRequestIndexID = env->GetFieldID(clazz.get(), "mIndex", "I"); + CHECK(gFields.queueRequestIndexID != NULL); + + clazz.reset(env->FindClass("android/media/MediaCodec$OutputFrame")); + CHECK(clazz.get() != NULL); + + gFields.outputFrameLinearBlockID = + env->GetFieldID(clazz.get(), "mLinearBlock", "Landroid/media/MediaCodec$LinearBlock;"); + CHECK(gFields.outputFrameLinearBlockID != NULL); + + gFields.outputFrameGraphicBlockID = + env->GetFieldID(clazz.get(), "mGraphicBlock", "Landroid/media/MediaCodec$GraphicBlock;"); + CHECK(gFields.outputFrameGraphicBlockID != NULL); + + gFields.outputFrameChangedKeysID = + env->GetFieldID(clazz.get(), "mChangedKeys", "Ljava/util/ArrayList;"); + CHECK(gFields.outputFrameChangedKeysID != NULL); + + gFields.outputFrameFormatID = + env->GetFieldID(clazz.get(), "mFormat", "Landroid/media/MediaFormat;"); + CHECK(gFields.outputFrameFormatID != NULL); + clazz.reset(env->FindClass("android/media/MediaCodec$CryptoException")); CHECK(clazz.get() != NULL); @@ -2105,6 +2557,96 @@ static void android_media_MediaCodec_native_init(JNIEnv *env) { field = env->GetFieldID(clazz.get(), "level", "I"); CHECK(field != NULL); gCodecInfo.levelField = field; + + clazz.reset(env->FindClass("java/nio/ByteBuffer")); + CHECK(clazz.get() != NULL); + gByteBufferInfo.clazz = (jclass)env->NewGlobalRef(clazz.get()); + + ScopedLocalRef<jclass> byteOrderClass( + env, env->FindClass("java/nio/ByteOrder")); + CHECK(byteOrderClass.get() != NULL); + + jmethodID nativeOrderID = env->GetStaticMethodID( + byteOrderClass.get(), "nativeOrder", "()Ljava/nio/ByteOrder;"); + CHECK(nativeOrderID != NULL); + + ScopedLocalRef<jobject> nativeByteOrderObj{ + env, env->CallStaticObjectMethod(byteOrderClass.get(), nativeOrderID)}; + gByteBufferInfo.nativeByteOrder = env->NewGlobalRef(nativeByteOrderObj.get()); + CHECK(gByteBufferInfo.nativeByteOrder != NULL); + nativeByteOrderObj.reset(); + + gByteBufferInfo.orderId = env->GetMethodID( + clazz.get(), + "order", + "(Ljava/nio/ByteOrder;)Ljava/nio/ByteBuffer;"); + CHECK(gByteBufferInfo.orderId != NULL); + + gByteBufferInfo.asReadOnlyBufferId = env->GetMethodID( + clazz.get(), "asReadOnlyBuffer", "()Ljava/nio/ByteBuffer;"); + CHECK(gByteBufferInfo.asReadOnlyBufferId != NULL); + + gByteBufferInfo.positionId = env->GetMethodID( + clazz.get(), "position", "(I)Ljava/nio/Buffer;"); + CHECK(gByteBufferInfo.positionId != NULL); + + gByteBufferInfo.limitId = env->GetMethodID( + clazz.get(), "limit", "(I)Ljava/nio/Buffer;"); + CHECK(gByteBufferInfo.limitId != NULL); + + clazz.reset(env->FindClass("java/util/ArrayList")); + CHECK(clazz.get() != NULL); + + gArrayListInfo.sizeId = env->GetMethodID(clazz.get(), "size", "()I"); + CHECK(gArrayListInfo.sizeId != NULL); + + gArrayListInfo.getId = env->GetMethodID(clazz.get(), "get", "(I)Ljava/lang/Object;"); + CHECK(gArrayListInfo.getId != NULL); + + gArrayListInfo.addId = env->GetMethodID(clazz.get(), "add", "(Ljava/lang/Object;)Z"); + CHECK(gArrayListInfo.addId != NULL); + + clazz.reset(env->FindClass("android/media/MediaCodec$LinearBlock")); + CHECK(clazz.get() != NULL); + + gLinearBlockInfo.clazz = (jclass)env->NewGlobalRef(clazz.get()); + + gLinearBlockInfo.ctorId = env->GetMethodID(clazz.get(), "<init>", "()V"); + CHECK(gLinearBlockInfo.ctorId != NULL); + + gLinearBlockInfo.setInternalStateId = env->GetMethodID( + clazz.get(), "setInternalStateLocked", "(JZ)V"); + CHECK(gLinearBlockInfo.setInternalStateId != NULL); + + gLinearBlockInfo.contextId = env->GetFieldID(clazz.get(), "mNativeContext", "J"); + CHECK(gLinearBlockInfo.contextId != NULL); + + gLinearBlockInfo.validId = env->GetFieldID(clazz.get(), "mValid", "Z"); + CHECK(gLinearBlockInfo.validId != NULL); + + gLinearBlockInfo.lockId = env->GetFieldID(clazz.get(), "mLock", "Ljava/lang/Object;"); + CHECK(gLinearBlockInfo.lockId != NULL); + + clazz.reset(env->FindClass("android/media/MediaCodec$GraphicBlock")); + CHECK(clazz.get() != NULL); + + gGraphicBlockInfo.clazz = (jclass)env->NewGlobalRef(clazz.get()); + + gGraphicBlockInfo.ctorId = env->GetMethodID(clazz.get(), "<init>", "()V"); + CHECK(gGraphicBlockInfo.ctorId != NULL); + + gGraphicBlockInfo.setInternalStateId = env->GetMethodID( + clazz.get(), "setInternalStateLocked", "(JZ)V"); + CHECK(gGraphicBlockInfo.setInternalStateId != NULL); + + gGraphicBlockInfo.contextId = env->GetFieldID(clazz.get(), "mNativeContext", "J"); + CHECK(gGraphicBlockInfo.contextId != NULL); + + gGraphicBlockInfo.validId = env->GetFieldID(clazz.get(), "mValid", "Z"); + CHECK(gGraphicBlockInfo.validId != NULL); + + gGraphicBlockInfo.lockId = env->GetFieldID(clazz.get(), "mLock", "Ljava/lang/Object;"); + CHECK(gGraphicBlockInfo.lockId != NULL); } static void android_media_MediaCodec_native_setup( @@ -2155,6 +2697,345 @@ static void android_media_MediaCodec_native_finalize( android_media_MediaCodec_release(env, thiz); } +// MediaCodec.LinearBlock + +static jobject android_media_MediaCodec_LinearBlock_native_map( + JNIEnv *env, jobject thiz) { + JMediaCodecLinearBlock *context = + (JMediaCodecLinearBlock *)env->GetLongField(thiz, gLinearBlockInfo.contextId); + if (context->mBuffer) { + std::shared_ptr<C2Buffer> buffer = context->mBuffer; + if (!context->mReadonlyMapping) { + const C2BufferData data = buffer->data(); + if (data.type() != C2BufferData::LINEAR) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return nullptr; + } + if (data.linearBlocks().size() != 1u) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return nullptr; + } + C2ConstLinearBlock block = data.linearBlocks().front(); + context->mReadonlyMapping = + std::make_shared<C2ReadView>(block.map().get()); + } + return CreateByteBuffer( + env, + context->mReadonlyMapping->data(), // base + context->mReadonlyMapping->capacity(), // capacity + 0u, // offset + context->mReadonlyMapping->capacity(), // size + true, // readOnly + true /* clearBuffer */); + } else if (context->mBlock) { + std::shared_ptr<C2LinearBlock> block = context->mBlock; + if (!context->mReadWriteMapping) { + context->mReadWriteMapping = + std::make_shared<C2WriteView>(block->map().get()); + } + return CreateByteBuffer( + env, + context->mReadWriteMapping->base(), + context->mReadWriteMapping->capacity(), + context->mReadWriteMapping->offset(), + context->mReadWriteMapping->size(), + false, // readOnly + true /* clearBuffer */); + } else if (context->mLegacyBuffer) { + return CreateByteBuffer( + env, + context->mLegacyBuffer->base(), + context->mLegacyBuffer->capacity(), + context->mLegacyBuffer->offset(), + context->mLegacyBuffer->size(), + true, // readOnly + true /* clearBuffer */); + } else if (context->mHeap) { + return CreateByteBuffer( + env, + static_cast<uint8_t *>(context->mHeap->getBase()) + context->mHeap->getOffset(), + context->mHeap->getSize(), + 0, + context->mHeap->getSize(), + false, // readOnly + true /* clearBuffer */); + } + throwExceptionAsNecessary(env, INVALID_OPERATION); + return nullptr; +} + +static void android_media_MediaCodec_LinearBlock_native_recycle( + JNIEnv *env, jobject thiz) { + JMediaCodecLinearBlock *context = + (JMediaCodecLinearBlock *)env->GetLongField(thiz, gLinearBlockInfo.contextId); + env->CallVoidMethod(thiz, gLinearBlockInfo.setInternalStateId, 0, false); + delete context; +} + +static void PopulateNamesVector( + JNIEnv *env, jobjectArray codecNames, std::vector<std::string> *names) { + jsize length = env->GetArrayLength(codecNames); + for (jsize i = 0; i < length; ++i) { + jstring jstr = static_cast<jstring>(env->GetObjectArrayElement(codecNames, i)); + if (jstr == nullptr) { + // null entries are ignored + continue; + } + const char *cstr = env->GetStringUTFChars(jstr, nullptr); + if (cstr == nullptr) { + throwExceptionAsNecessary(env, BAD_VALUE); + return; + } + names->emplace_back(cstr); + env->ReleaseStringUTFChars(jstr, cstr); + } +} + +static void android_media_MediaCodec_LinearBlock_native_obtain( + JNIEnv *env, jobject thiz, jint capacity, jobjectArray codecNames) { + std::unique_ptr<JMediaCodecLinearBlock> context{new JMediaCodecLinearBlock}; + std::vector<std::string> names; + PopulateNamesVector(env, codecNames, &names); + bool hasSecure = false; + bool hasNonSecure = false; + for (const std::string &name : names) { + if (name.length() >= 7 && name.substr(name.length() - 7) == ".secure") { + hasSecure = true; + } else { + hasNonSecure = true; + } + } + if (hasSecure && !hasNonSecure) { + context->mHeap = new MemoryHeapBase(capacity); + context->mMemory = hardware::fromHeap(context->mHeap); + } else { + context->mBlock = MediaCodec::FetchLinearBlock(capacity, names); + if (!context->mBlock) { + jniThrowException(env, "java/io/IOException", nullptr); + return; + } + } + env->CallVoidMethod( + thiz, + gLinearBlockInfo.setInternalStateId, + (jlong)context.release(), + true /* isMappable */); +} + +static jboolean android_media_MediaCodec_LinearBlock_checkCompatible( + JNIEnv *env, jobjectArray codecNames) { + std::vector<std::string> names; + PopulateNamesVector(env, codecNames, &names); + bool isCompatible = false; + bool hasSecure = false; + bool hasNonSecure = false; + for (const std::string &name : names) { + if (name.length() >= 7 && name.substr(name.length() - 7) == ".secure") { + hasSecure = true; + } else { + hasNonSecure = true; + } + } + if (hasSecure && hasNonSecure) { + return false; + } + status_t err = MediaCodec::CanFetchLinearBlock(names, &isCompatible); + if (err != OK) { + throwExceptionAsNecessary(env, err); + } + return isCompatible; +} + +// MediaCodec.GraphicBlock + +template <class T> +static jobject CreateImage(JNIEnv *env, const std::shared_ptr<T> &view) { + bool readOnly = std::is_const<T>::value; + const C2PlanarLayout layout = view->layout(); + jint format = HAL_PIXEL_FORMAT_YCBCR_420_888; + switch (layout.type) { + case C2PlanarLayout::TYPE_YUV: { + if (layout.numPlanes != 3) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return nullptr; + } + const C2PlaneInfo & yPlane = layout.planes[C2PlanarLayout::PLANE_Y]; + const C2PlaneInfo & uPlane = layout.planes[C2PlanarLayout::PLANE_U]; + const C2PlaneInfo & vPlane = layout.planes[C2PlanarLayout::PLANE_V]; + if (yPlane.rowSampling != 1 || yPlane.colSampling != 1) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return nullptr; + } + if (uPlane.rowSampling != vPlane.rowSampling + || uPlane.colSampling != vPlane.colSampling) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return nullptr; + } + if (uPlane.rowSampling == 2 && uPlane.colSampling == 2) { + format = HAL_PIXEL_FORMAT_YCBCR_420_888; + break; + } else if (uPlane.rowSampling == 1 && uPlane.colSampling == 2) { + format = HAL_PIXEL_FORMAT_YCBCR_422_888; + break; + } else if (uPlane.rowSampling == 1 && uPlane.colSampling == 1) { + format = HAL_PIXEL_FORMAT_YCBCR_444_888; + break; + } + throwExceptionAsNecessary(env, INVALID_OPERATION); + return nullptr; + } + case C2PlanarLayout::TYPE_RGB: { + if (layout.numPlanes != 3) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return nullptr; + } + format = HAL_PIXEL_FORMAT_FLEX_RGB_888; + break; + } + case C2PlanarLayout::TYPE_RGBA: { + if (layout.numPlanes != 4) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return nullptr; + } + format = HAL_PIXEL_FORMAT_FLEX_RGBA_8888; + break; + } + case C2PlanarLayout::TYPE_YUVA: + [[fallthrough]]; + default: + throwExceptionAsNecessary(env, INVALID_OPERATION); + return nullptr; + } + + ScopedLocalRef<jclass> planeClazz( + env, env->FindClass("android/media/MediaCodec$MediaImage$MediaPlane")); + ScopedLocalRef<jobjectArray> planeArray{ + env, env->NewObjectArray(layout.numPlanes, planeClazz.get(), NULL)}; + CHECK(planeClazz.get() != NULL); + jmethodID planeConstructID = env->GetMethodID(planeClazz.get(), "<init>", + "([Ljava/nio/ByteBuffer;IIIII)V"); + + // plane indices are happened to be Y-U-V and R-G-B(-A) order. + for (uint32_t i = 0; i < layout.numPlanes; ++i) { + const C2PlaneInfo &plane = layout.planes[i]; + if (plane.rowInc < 0 || plane.colInc < 0) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return nullptr; + } + ssize_t minOffset = plane.minOffset(view->width(), view->height()); + ssize_t maxOffset = plane.maxOffset(view->width(), view->height()); + ScopedLocalRef<jobject> byteBuffer{env, CreateByteBuffer( + env, + view->data()[plane.rootIx] + plane.offset + minOffset, + maxOffset - minOffset + 1, + 0, + maxOffset - minOffset + 1, + readOnly, + true)}; + + ScopedLocalRef<jobject> jPlane{env, env->NewObject( + planeClazz.get(), planeConstructID, + byteBuffer.get(), plane.rowInc, plane.colInc)}; + } + + ScopedLocalRef<jclass> imageClazz( + env, env->FindClass("android/media/MediaCodec$MediaImage")); + CHECK(imageClazz.get() != NULL); + + jmethodID imageConstructID = env->GetMethodID(imageClazz.get(), "<init>", + "([Landroid/media/Image$Plane;IIIZJIILandroid/graphics/Rect;)V"); + + jobject img = env->NewObject(imageClazz.get(), imageConstructID, + planeArray.get(), + view->width(), + view->height(), + format, + (jboolean)readOnly /* readOnly */, + (jlong)0 /* timestamp */, + (jint)0 /* xOffset */, (jint)0 /* yOffset */, nullptr /* cropRect */); + + // if MediaImage creation fails, return null + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return nullptr; + } + + return img; +} + +static jobject android_media_MediaCodec_GraphicBlock_native_map( + JNIEnv *env, jobject thiz) { + JMediaCodecGraphicBlock *context = + (JMediaCodecGraphicBlock *)env->GetLongField(thiz, gGraphicBlockInfo.contextId); + if (context->mBuffer) { + std::shared_ptr<C2Buffer> buffer = context->mBuffer; + if (!context->mReadonlyMapping) { + const C2BufferData data = buffer->data(); + if (data.type() != C2BufferData::GRAPHIC) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return nullptr; + } + if (data.graphicBlocks().size() != 1u) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return nullptr; + } + C2ConstGraphicBlock block = data.graphicBlocks().front(); + context->mReadonlyMapping = + std::make_shared<const C2GraphicView>(block.map().get()); + } + return CreateImage(env, context->mReadonlyMapping); + } else if (context->mBlock) { + std::shared_ptr<C2GraphicBlock> block = context->mBlock; + if (!context->mReadWriteMapping) { + context->mReadWriteMapping = + std::make_shared<C2GraphicView>(block->map().get()); + } + return CreateImage(env, context->mReadWriteMapping); + } else if (context->mLegacyBuffer) { + } + throwExceptionAsNecessary(env, INVALID_OPERATION); + return nullptr; +} + +static void android_media_MediaCodec_GraphicBlock_native_recycle( + JNIEnv *env, jobject thiz) { + JMediaCodecGraphicBlock *context = + (JMediaCodecGraphicBlock *)env->GetLongField(thiz, gGraphicBlockInfo.contextId); + env->CallVoidMethod(thiz, gGraphicBlockInfo.setInternalStateId, 0, false /* isMappable */); + delete context; +} + +static void android_media_MediaCodec_GraphicBlock_native_obtain( + JNIEnv *env, jobject thiz, + jint width, jint height, jint format, jlong usage, jobjectArray codecNames) { + std::unique_ptr<JMediaCodecGraphicBlock> context{new JMediaCodecGraphicBlock}; + std::vector<std::string> names; + PopulateNamesVector(env, codecNames, &names); + context->mBlock = MediaCodec::FetchGraphicBlock(width, height, format, usage, names); + if (!context->mBlock) { + jniThrowException(env, "java/io/IOException", nullptr); + return; + } + env->CallVoidMethod( + thiz, + gGraphicBlockInfo.setInternalStateId, + (jlong)context.release(), + true /*isMappable */); +} + +static jboolean android_media_MediaCodec_GraphicBlock_checkCompatible( + JNIEnv *env, jobjectArray codecNames) { + std::vector<std::string> names; + PopulateNamesVector(env, codecNames, &names); + bool isCompatible = false; + status_t err = MediaCodec::CanFetchGraphicBlock(names, &isCompatible); + if (err != OK) { + throwExceptionAsNecessary(env, err); + } + return isCompatible; +} + static const JNINativeMethod gMethods[] = { { "native_release", "()V", (void *)android_media_MediaCodec_release }, @@ -2200,6 +3081,19 @@ static const JNINativeMethod gMethods[] = { { "native_queueSecureInputBuffer", "(IILandroid/media/MediaCodec$CryptoInfo;JI)V", (void *)android_media_MediaCodec_queueSecureInputBuffer }, + { "native_queueLinearBlock", + "(ILandroid/media/MediaCodec$LinearBlock;IILandroid/media/MediaCodec$CryptoInfo;JI" + "Ljava/util/ArrayList;Ljava/util/ArrayList;)V", + (void *)android_media_MediaCodec_native_queueLinearBlock }, + + { "native_queueGraphicBlock", + "(ILandroid/media/MediaCodec$GraphicBlock;JILjava/util/ArrayList;Ljava/util/ArrayList;)V", + (void *)android_media_MediaCodec_native_queueGraphicBlock }, + + { "native_getOutputFrame", + "(Landroid/media/MediaCodec$OutputFrame;I)V", + (void *)android_media_MediaCodec_native_getOutputFrame }, + { "native_dequeueInputBuffer", "(J)I", (void *)android_media_MediaCodec_dequeueInputBuffer }, @@ -2254,7 +3148,50 @@ static const JNINativeMethod gMethods[] = { (void *)android_media_MediaCodec_native_finalize }, }; +static const JNINativeMethod gLinearBlockMethods[] = { + { "native_map", "()Ljava/nio/ByteBuffer;", + (void *)android_media_MediaCodec_LinearBlock_native_map }, + + { "native_recycle", "()V", + (void *)android_media_MediaCodec_LinearBlock_native_recycle }, + + { "native_obtain", "(I[Ljava/lang/String;)V", + (void *)android_media_MediaCodec_LinearBlock_native_obtain }, + + { "native_checkCompatible", "([Ljava/lang/String;)Z", + (void *)android_media_MediaCodec_LinearBlock_checkCompatible }, +}; + +static const JNINativeMethod gGraphicBlockMethods[] = { + { "native_map", "()Landroid/media/Image;", + (void *)android_media_MediaCodec_GraphicBlock_native_map }, + + { "native_recycle", "()V", + (void *)android_media_MediaCodec_GraphicBlock_native_recycle }, + + { "native_obtain", "(IIIJ[Ljava/lang/String;)V", + (void *)android_media_MediaCodec_GraphicBlock_native_obtain }, + + { "native_checkCompatible", "([Ljava/lang/String;)Z", + (void *)android_media_MediaCodec_GraphicBlock_checkCompatible }, +}; + int register_android_media_MediaCodec(JNIEnv *env) { - return AndroidRuntime::registerNativeMethods(env, + int result = AndroidRuntime::registerNativeMethods(env, "android/media/MediaCodec", gMethods, NELEM(gMethods)); + if (result != JNI_OK) { + return result; + } + result = AndroidRuntime::registerNativeMethods(env, + "android/media/MediaCodec$LinearBlock", + gLinearBlockMethods, + NELEM(gLinearBlockMethods)); + if (result != JNI_OK) { + return result; + } + result = AndroidRuntime::registerNativeMethods(env, + "android/media/MediaCodec$GraphicBlock", + gGraphicBlockMethods, + NELEM(gGraphicBlockMethods)); + return result; } diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h index ce1c805b6366..1d12e776d43e 100644 --- a/media/jni/android_media_MediaCodec.h +++ b/media/jni/android_media_MediaCodec.h @@ -27,6 +27,8 @@ #include <media/stagefright/foundation/AHandler.h> #include <utils/Errors.h> +class C2Buffer; + namespace android { struct ABuffer; @@ -39,6 +41,7 @@ struct MediaCodec; struct PersistentSurface; class Surface; namespace hardware { +class HidlMemory; namespace cas { namespace native { namespace V1_0 { @@ -97,6 +100,26 @@ struct JMediaCodec : public AHandler { uint32_t flags, AString *errorDetailMsg); + status_t queueBuffer( + size_t index, const std::shared_ptr<C2Buffer> &buffer, + int64_t timeUs, uint32_t flags, const sp<AMessage> &tunings, + AString *errorDetailMsg); + + status_t queueEncryptedLinearBlock( + size_t index, + const sp<hardware::HidlMemory> &buffer, + size_t offset, + const CryptoPlugin::SubSample *subSamples, + size_t numSubSamples, + const uint8_t key[16], + const uint8_t iv[16], + CryptoPlugin::Mode mode, + const CryptoPlugin::Pattern &pattern, + int64_t presentationTimeUs, + uint32_t flags, + const sp<AMessage> &tunings, + AString *errorDetailMsg); + status_t dequeueInputBuffer(size_t *index, int64_t timeoutUs); status_t dequeueOutputBuffer( @@ -120,6 +143,9 @@ struct JMediaCodec : public AHandler { status_t getImage( JNIEnv *env, bool input, size_t index, jobject *image) const; + status_t getOutputFrame( + JNIEnv *env, jobject frame, size_t index) const; + status_t getName(JNIEnv *env, jstring *name) const; status_t getCodecInfo(JNIEnv *env, jobject *codecInfo) const; @@ -147,17 +173,10 @@ private: jweak mObject; sp<Surface> mSurfaceTextureClient; - // java objects cached - jclass mByteBufferClass; - jobject mNativeByteOrderObj; - jmethodID mByteBufferOrderMethodID; - jmethodID mByteBufferPositionMethodID; - jmethodID mByteBufferLimitMethodID; - jmethodID mByteBufferAsReadOnlyBufferMethodID; - sp<ALooper> mLooper; sp<MediaCodec> mCodec; AString mNameAtCreation; + bool mGraphicOutput{false}; std::once_flag mReleaseFlag; sp<AMessage> mCallbackNotification; @@ -170,8 +189,6 @@ private: JNIEnv *env, bool readOnly, bool clearBuffer, const sp<T> &buffer, jobject *buf) const; - void cacheJavaObjects(JNIEnv *env); - void deleteJavaObjects(JNIEnv *env); void handleCallback(const sp<AMessage> &msg); void handleFrameRenderedNotification(const sp<AMessage> &msg); diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java index 7726e90674f2..4a2044af0431 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java @@ -246,7 +246,7 @@ public class MediaRouterManagerTest { } }); - assertEquals(0, mManager.getRoutingControllers(mPackageName).size()); + assertEquals(1, mManager.getRoutingControllers(mPackageName).size()); mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1)); latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); @@ -254,14 +254,14 @@ public class MediaRouterManagerTest { List<MediaRouter2Manager.RoutingController> controllers = mManager.getRoutingControllers(mPackageName); - assertEquals(1, controllers.size()); + assertEquals(2, controllers.size()); - MediaRouter2Manager.RoutingController routingController = controllers.get(0); + MediaRouter2Manager.RoutingController routingController = controllers.get(1); awaitOnRouteChangedManager( () -> routingController.release(), ROUTE_ID1, route -> TextUtils.equals(route.getClientPackageName(), null)); - assertEquals(0, mManager.getRoutingControllers(mPackageName).size()); + assertEquals(1, mManager.getRoutingControllers(mPackageName).size()); } /** @@ -290,8 +290,8 @@ public class MediaRouterManagerTest { List<MediaRouter2Manager.RoutingController> controllers = mManager.getRoutingControllers(mPackageName); - assertEquals(1, controllers.size()); - MediaRouter2Manager.RoutingController routingController = controllers.get(0); + assertEquals(2, controllers.size()); + MediaRouter2Manager.RoutingController routingController = controllers.get(1); awaitOnRouteChangedManager( () -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID5_TO_TRANSFER_TO)), diff --git a/native/graphics/jni/bitmap.cpp b/native/graphics/jni/bitmap.cpp index ea8a521c9d5f..b8eb543ff835 100644 --- a/native/graphics/jni/bitmap.cpp +++ b/native/graphics/jni/bitmap.cpp @@ -33,10 +33,14 @@ int AndroidBitmap_getInfo(JNIEnv* env, jobject jbitmap, int32_t AndroidBitmap_getDataSpace(JNIEnv* env, jobject jbitmap) { if (NULL == env || NULL == jbitmap) { - return ADATASPACE_UNKNOWN; // Or return a real error? + return ADATASPACE_UNKNOWN; } android::graphics::Bitmap bitmap(env, jbitmap); + if (!bitmap.isValid()) { + return ADATASPACE_UNKNOWN; + } + return bitmap.getDataSpace(); } @@ -76,12 +80,27 @@ int AndroidBitmap_unlockPixels(JNIEnv* env, jobject jbitmap) { return ANDROID_BITMAP_RESULT_SUCCESS; } +int AndroidBitmap_getHardwareBuffer(JNIEnv* env, jobject jbitmap, AHardwareBuffer** outBuffer) { + if (NULL == env || NULL == jbitmap || NULL == outBuffer) { + return ANDROID_BITMAP_RESULT_BAD_PARAMETER; + } + + android::graphics::Bitmap bitmap(env, jbitmap); + + if (!bitmap.isValid()) { + return ANDROID_BITMAP_RESULT_JNI_EXCEPTION; + } + + *outBuffer = bitmap.getHardwareBuffer(); + return *outBuffer == NULL ? ANDROID_BITMAP_RESULT_BAD_PARAMETER : ANDROID_BITMAP_RESULT_SUCCESS; +} + int AndroidBitmap_compress(const AndroidBitmapInfo* info, int32_t dataSpace, const void* pixels, int32_t format, int32_t quality, void* userContext, - AndroidBitmap_compress_write_fn fn) { + AndroidBitmap_CompressWriteFunc fn) { if (NULL == info || NULL == pixels || NULL == fn) { return ANDROID_BITMAP_RESULT_BAD_PARAMETER; } diff --git a/native/graphics/jni/imagedecoder.cpp b/native/graphics/jni/imagedecoder.cpp index 51439672d404..1c45ea6aaecc 100644 --- a/native/graphics/jni/imagedecoder.cpp +++ b/native/graphics/jni/imagedecoder.cpp @@ -18,12 +18,14 @@ #include <android/asset_manager.h> #include <android/bitmap.h> +#include <android/data_space.h> #include <android/imagedecoder.h> #include <android/graphics/MimeType.h> #include <android/rect.h> #include <hwui/ImageDecoder.h> #include <log/log.h> #include <SkAndroidCodec.h> +#include <utils/Color.h> #include <fcntl.h> #include <optional> @@ -131,6 +133,10 @@ static ImageDecoder* toDecoder(AImageDecoder* d) { return reinterpret_cast<ImageDecoder*>(d); } +static const ImageDecoder* toDecoder(const AImageDecoder* d) { + return reinterpret_cast<const ImageDecoder*>(d); +} + // Note: This differs from the version in android_bitmap.cpp in that this // version returns kGray_8_SkColorType for ANDROID_BITMAP_FORMAT_A_8. SkCodec // allows decoding single channel images to gray, which Android then treats @@ -161,6 +167,18 @@ int AImageDecoder_setAndroidBitmapFormat(AImageDecoder* decoder, int32_t format) ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_INVALID_CONVERSION; } +int AImageDecoder_setDataSpace(AImageDecoder* decoder, int32_t dataspace) { + sk_sp<SkColorSpace> cs = uirenderer::DataSpaceToColorSpace((android_dataspace)dataspace); + // 0 is ADATASPACE_UNKNOWN. We need an explicit request for an ADataSpace. + if (!decoder || !dataspace || !cs) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + + ImageDecoder* imageDecoder = toDecoder(decoder); + imageDecoder->setOutColorSpace(std::move(cs)); + return ANDROID_IMAGE_DECODER_SUCCESS; +} + const AImageDecoderHeaderInfo* AImageDecoder_getHeaderInfo(const AImageDecoder* decoder) { return reinterpret_cast<const AImageDecoderHeaderInfo*>(decoder); } @@ -197,6 +215,20 @@ bool AImageDecoderHeaderInfo_isAnimated(const AImageDecoderHeaderInfo* info) { return toDecoder(info)->mCodec->codec()->getFrameCount() > 1; } +int32_t AImageDecoderHeaderInfo_getDataSpace(const AImageDecoderHeaderInfo* info) { + if (!info) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + + // Note: This recomputes the data space because it's possible the client has + // changed the output color space, so we cannot rely on it. Alternatively, + // we could store the ADataSpace in the ImageDecoder. + const ImageDecoder* imageDecoder = toDecoder(info); + SkColorType colorType = imageDecoder->mCodec->computeOutputColorType(kN32_SkColorType); + sk_sp<SkColorSpace> colorSpace = imageDecoder->mCodec->computeOutputColorSpace(colorType); + return uirenderer::ColorSpaceToADataSpace(colorSpace.get(), colorType); +} + // FIXME: Share with getFormat in android_bitmap.cpp? static AndroidBitmapFormat getFormat(SkColorType colorType) { switch (colorType) { @@ -258,6 +290,18 @@ int AImageDecoder_setTargetSize(AImageDecoder* decoder, int width, int height) { ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_INVALID_SCALE; } +int AImageDecoder_computeSampledSize(const AImageDecoder* decoder, int sampleSize, + int* width, int* height) { + if (!decoder || !width || !height || sampleSize < 1) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + + SkISize size = toDecoder(decoder)->mCodec->getSampledDimensions(sampleSize); + *width = size.width(); + *height = size.height(); + return ANDROID_IMAGE_DECODER_SUCCESS; +} + int AImageDecoder_setCrop(AImageDecoder* decoder, ARect crop) { if (!decoder) { return ANDROID_IMAGE_DECODER_BAD_PARAMETER; diff --git a/native/graphics/jni/libjnigraphics.map.txt b/native/graphics/jni/libjnigraphics.map.txt index 6843e7a8552f..1b396b893f4e 100644 --- a/native/graphics/jni/libjnigraphics.map.txt +++ b/native/graphics/jni/libjnigraphics.map.txt @@ -6,10 +6,12 @@ LIBJNIGRAPHICS { AImageDecoder_delete; # introduced=30 AImageDecoder_setAndroidBitmapFormat; # introduced=30 AImageDecoder_setUnpremultipliedRequired; # introduced=30 + AImageDecoder_setDataSpace; # introduced=30 AImageDecoder_getHeaderInfo; # introduced=30 AImageDecoder_getMinimumStride; # introduced=30 AImageDecoder_decodeImage; # introduced=30 AImageDecoder_setTargetSize; # introduced=30 + AImageDecoder_computeSampledSize; # introduced=30 AImageDecoder_setCrop; # introduced=30 AImageDecoderHeaderInfo_getWidth; # introduced=30 AImageDecoderHeaderInfo_getHeight; # introduced=30 @@ -17,11 +19,13 @@ LIBJNIGRAPHICS { AImageDecoderHeaderInfo_getAlphaFlags; # introduced=30 AImageDecoderHeaderInfo_isAnimated; # introduced=30 AImageDecoderHeaderInfo_getAndroidBitmapFormat; # introduced=30 + AImageDecoderHeaderInfo_getDataSpace; # introduced=30 AndroidBitmap_getInfo; AndroidBitmap_getDataSpace; AndroidBitmap_lockPixels; AndroidBitmap_unlockPixels; AndroidBitmap_compress; # introduced=30 + AndroidBitmap_getHardwareBuffer; #introduced=30 local: *; }; diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java index 8c756ecbaefc..34baa2de567f 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java @@ -20,6 +20,7 @@ import static android.content.DialogInterface.BUTTON_NEGATIVE; import static android.content.DialogInterface.BUTTON_POSITIVE; import static android.os.UserManager.DISALLOW_ADD_USER; import static android.os.UserManager.SWITCHABILITY_STATUS_OK; +import static android.view.WindowInsets.Type.statusBars; import android.annotation.IntDef; import android.annotation.Nullable; @@ -367,8 +368,8 @@ public class UserGridRecyclerView extends RecyclerView { window.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL); window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); - window.setFitWindowInsetsTypes( - window.getFitWindowInsetsTypes() & ~WindowInsets.Type.statusBars()); + window.getAttributes().setFitWindowInsetsTypes( + window.getAttributes().getFitWindowInsetsTypes() & ~statusBars()); } private void notifyUserSelected(UserRecord userRecord) { diff --git a/packages/FusedLocation/Android.bp b/packages/FusedLocation/Android.bp index e794f726dba6..c70ab716aa44 100644 --- a/packages/FusedLocation/Android.bp +++ b/packages/FusedLocation/Android.bp @@ -14,9 +14,33 @@ android_app { name: "FusedLocation", - srcs: ["**/*.java"], + srcs: ["src/**/*.java"], libs: ["com.android.location.provider"], platform_apis: true, certificate: "platform", privileged: true, } + +android_test { + name: "FusedLocationTests", + manifest: "test/AndroidManifest.xml", + test_config: "test/AndroidTest.xml", + srcs: [ + "test/src/**/*.java", + "src/**/*.java", // include real sources because we're forced to test this directly + ], + libs: [ + "android.test.base", + "android.test.runner", + "com.android.location.provider", + ], + static_libs: [ + "androidx.test.core", + "androidx.test.rules", + "androidx.test.ext.junit", + "androidx.test.ext.truth", + "mockito-target-minus-junit4", + "truth-prebuilt", + ], + test_suites: ["device-tests"] +}
\ No newline at end of file diff --git a/packages/FusedLocation/AndroidManifest.xml b/packages/FusedLocation/AndroidManifest.xml index a8319ab233ac..bad0497c487d 100644 --- a/packages/FusedLocation/AndroidManifest.xml +++ b/packages/FusedLocation/AndroidManifest.xml @@ -23,6 +23,8 @@ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> + <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> + <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" /> <uses-permission android:name="android.permission.INSTALL_LOCATION_PROVIDER" /> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> diff --git a/packages/FusedLocation/TEST_MAPPING b/packages/FusedLocation/TEST_MAPPING new file mode 100644 index 000000000000..e810d6aea8af --- /dev/null +++ b/packages/FusedLocation/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "FusedLocationTests" + } + ] +}
\ No newline at end of file diff --git a/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java b/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java index be817d60e55b..fb7dbc8aca5c 100644 --- a/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java +++ b/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java @@ -16,70 +16,307 @@ package com.android.location.fused; +import static android.content.Intent.ACTION_USER_SWITCHED; +import static android.location.LocationManager.GPS_PROVIDER; +import static android.location.LocationManager.NETWORK_PROVIDER; + +import android.annotation.Nullable; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.location.Criteria; -import android.os.Handler; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.location.LocationRequest; +import android.os.Bundle; import android.os.Looper; -import android.os.UserHandle; +import android.os.Parcelable; import android.os.WorkSource; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.location.ProviderRequest; import com.android.location.provider.LocationProviderBase; +import com.android.location.provider.LocationRequestUnbundled; import com.android.location.provider.ProviderPropertiesUnbundled; import com.android.location.provider.ProviderRequestUnbundled; -import java.io.FileDescriptor; import java.io.PrintWriter; -class FusedLocationProvider extends LocationProviderBase implements FusionEngine.Callback { +/** Basic fused location provider implementation. */ +public class FusedLocationProvider extends LocationProviderBase { + private static final String TAG = "FusedLocationProvider"; - private static ProviderPropertiesUnbundled PROPERTIES = ProviderPropertiesUnbundled.create( - false, false, false, false, true, true, true, Criteria.POWER_LOW, - Criteria.ACCURACY_FINE); + private static final ProviderPropertiesUnbundled PROPERTIES = + ProviderPropertiesUnbundled.create( + /* requiresNetwork = */ false, + /* requiresSatellite = */ false, + /* requiresCell = */ false, + /* hasMonetaryCost = */ false, + /* supportsAltitude = */ true, + /* supportsSpeed = */ true, + /* supportsBearing = */ true, + Criteria.POWER_LOW, + Criteria.ACCURACY_FINE + ); + + private static final long MAX_LOCATION_COMPARISON_NS = 11 * 1000000000L; // 11 seconds + + private final Object mLock = new Object(); private final Context mContext; - private final Handler mHandler; - private final FusionEngine mEngine; - - private final BroadcastReceiver mUserSwitchReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (Intent.ACTION_USER_SWITCHED.equals(action)) { - mEngine.switchUser(); - } - } - }; + private final LocationManager mLocationManager; + private final LocationListener mGpsListener; + private final LocationListener mNetworkListener; + private final BroadcastReceiver mUserChangeReceiver; - FusedLocationProvider(Context context) { - super(TAG, PROPERTIES); + @GuardedBy("mLock") + private ProviderRequestUnbundled mRequest; + @GuardedBy("mLock") + private WorkSource mWorkSource; + @GuardedBy("mLock") + private long mGpsInterval; + @GuardedBy("mLock") + private long mNetworkInterval; + @GuardedBy("mLock") + @Nullable private Location mFusedLocation; + @GuardedBy("mLock") + @Nullable private Location mGpsLocation; + @GuardedBy("mLock") + @Nullable private Location mNetworkLocation; + + public FusedLocationProvider(Context context) { + super(TAG, PROPERTIES); mContext = context; - mHandler = new Handler(Looper.myLooper()); - mEngine = new FusionEngine(context, Looper.myLooper(), this); + mLocationManager = context.getSystemService(LocationManager.class); + + mGpsListener = new LocationListener() { + @Override + public void onLocationChanged(Location location) { + synchronized (mLock) { + mGpsLocation = location; + reportBestLocationLocked(); + } + } + + @Override + public void onProviderDisabled(String provider) { + synchronized (mLock) { + // if satisfying a bypass request, don't clear anything + if (mRequest.getReportLocation() && mRequest.isLocationSettingsIgnored()) { + return; + } + + mGpsLocation = null; + } + } + }; + + mNetworkListener = new LocationListener() { + @Override + public void onLocationChanged(Location location) { + synchronized (mLock) { + mNetworkLocation = location; + reportBestLocationLocked(); + } + } + + @Override + public void onProviderDisabled(String provider) { + synchronized (mLock) { + // if satisfying a bypass request, don't clear anything + if (mRequest.getReportLocation() && mRequest.isLocationSettingsIgnored()) { + return; + } + + mNetworkLocation = null; + } + } + }; + + mUserChangeReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (!ACTION_USER_SWITCHED.equals(intent.getAction())) { + return; + } + + onUserChanged(); + } + }; + + mRequest = new ProviderRequestUnbundled(ProviderRequest.EMPTY_REQUEST); + mWorkSource = new WorkSource(); + mGpsInterval = Long.MAX_VALUE; + mNetworkInterval = Long.MAX_VALUE; } - void init() { - // listen for user change - mContext.registerReceiverAsUser(mUserSwitchReceiver, UserHandle.ALL, - new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler); + void start() { + mContext.registerReceiver(mUserChangeReceiver, new IntentFilter(ACTION_USER_SWITCHED)); } - void destroy() { - mContext.unregisterReceiver(mUserSwitchReceiver); - mHandler.post(() -> mEngine.setRequest(null)); + void stop() { + mContext.unregisterReceiver(mUserChangeReceiver); + + synchronized (mLock) { + mRequest = new ProviderRequestUnbundled(ProviderRequest.EMPTY_REQUEST); + updateRequirementsLocked(); + } } @Override - public void onSetRequest(ProviderRequestUnbundled request, WorkSource source) { - mHandler.post(() -> mEngine.setRequest(request)); + public void onSetRequest(ProviderRequestUnbundled request, WorkSource workSource) { + synchronized (mLock) { + mRequest = request; + mWorkSource = workSource; + updateRequirementsLocked(); + } } - @Override - public void onDump(FileDescriptor fd, PrintWriter pw, String[] args) { - mEngine.dump(fd, pw, args); + @GuardedBy("mLock") + private void updateRequirementsLocked() { + long gpsInterval = Long.MAX_VALUE; + long networkInterval = Long.MAX_VALUE; + if (mRequest.getReportLocation()) { + for (LocationRequestUnbundled request : mRequest.getLocationRequests()) { + switch (request.getQuality()) { + case LocationRequestUnbundled.ACCURACY_FINE: + case LocationRequestUnbundled.POWER_HIGH: + if (request.getInterval() < gpsInterval) { + gpsInterval = request.getInterval(); + } + if (request.getInterval() < networkInterval) { + networkInterval = request.getInterval(); + } + break; + case LocationRequestUnbundled.ACCURACY_BLOCK: + case LocationRequestUnbundled.ACCURACY_CITY: + case LocationRequestUnbundled.POWER_LOW: + if (request.getInterval() < networkInterval) { + networkInterval = request.getInterval(); + } + break; + } + } + } + + if (gpsInterval != mGpsInterval) { + resetProviderRequestLocked(GPS_PROVIDER, mGpsInterval, gpsInterval, mGpsListener); + mGpsInterval = gpsInterval; + } + if (networkInterval != mNetworkInterval) { + resetProviderRequestLocked(NETWORK_PROVIDER, mNetworkInterval, networkInterval, + mNetworkListener); + mNetworkInterval = networkInterval; + } + } + + @GuardedBy("mLock") + private void resetProviderRequestLocked(String provider, long oldInterval, long newInterval, + LocationListener listener) { + if (oldInterval != Long.MAX_VALUE) { + mLocationManager.removeUpdates(listener); + } + if (newInterval != Long.MAX_VALUE) { + LocationRequest request = LocationRequest.createFromDeprecatedProvider( + provider, newInterval, 0, false); + if (mRequest.isLocationSettingsIgnored()) { + request.setLocationSettingsIgnored(true); + } + request.setWorkSource(mWorkSource); + mLocationManager.requestLocationUpdates(request, listener, Looper.getMainLooper()); + } + } + + @GuardedBy("mLock") + private void reportBestLocationLocked() { + Location bestLocation = chooseBestLocation(mGpsLocation, mNetworkLocation); + if (bestLocation == mFusedLocation) { + return; + } + + mFusedLocation = bestLocation; + if (mFusedLocation == null) { + return; + } + + // copy NO_GPS_LOCATION extra from mNetworkLocation into mFusedLocation + if (mNetworkLocation != null) { + Bundle srcExtras = mNetworkLocation.getExtras(); + if (srcExtras != null) { + Parcelable srcParcelable = + srcExtras.getParcelable(LocationProviderBase.EXTRA_NO_GPS_LOCATION); + if (srcParcelable instanceof Location) { + Bundle dstExtras = mFusedLocation.getExtras(); + if (dstExtras == null) { + dstExtras = new Bundle(); + mFusedLocation.setExtras(dstExtras); + } + dstExtras.putParcelable(LocationProviderBase.EXTRA_NO_GPS_LOCATION, + srcParcelable); + } + } + } + + reportLocation(mFusedLocation); + } + + private void onUserChanged() { + // clear cached locations when the user changes to prevent leaking user information + synchronized (mLock) { + mFusedLocation = null; + mGpsLocation = null; + mNetworkLocation = null; + } + } + + void dump(PrintWriter writer) { + synchronized (mLock) { + writer.println("request: " + mRequest); + if (mGpsInterval != Long.MAX_VALUE) { + writer.println(" gps interval: " + mGpsInterval); + } + if (mNetworkInterval != Long.MAX_VALUE) { + writer.println(" network interval: " + mNetworkInterval); + } + if (mGpsLocation != null) { + writer.println(" last gps location: " + mGpsLocation); + } + if (mNetworkLocation != null) { + writer.println(" last network location: " + mNetworkLocation); + } + } + } + + @Nullable + private static Location chooseBestLocation( + @Nullable Location locationA, + @Nullable Location locationB) { + if (locationA == null) { + return locationB; + } + if (locationB == null) { + return locationA; + } + + if (locationA.getElapsedRealtimeNanos() + > locationB.getElapsedRealtimeNanos() + MAX_LOCATION_COMPARISON_NS) { + return locationA; + } + if (locationB.getElapsedRealtimeNanos() + > locationA.getElapsedRealtimeNanos() + MAX_LOCATION_COMPARISON_NS) { + return locationB; + } + + if (!locationA.hasAccuracy()) { + return locationB; + } + if (!locationB.hasAccuracy()) { + return locationA; + } + return locationA.getAccuracy() < locationB.getAccuracy() ? locationA : locationB; } } diff --git a/packages/FusedLocation/src/com/android/location/fused/FusedLocationService.java b/packages/FusedLocation/src/com/android/location/fused/FusedLocationService.java index 75bb5eceab6d..1fa3824f2321 100644 --- a/packages/FusedLocation/src/com/android/location/fused/FusedLocationService.java +++ b/packages/FusedLocation/src/com/android/location/fused/FusedLocationService.java @@ -16,19 +16,23 @@ package com.android.location.fused; +import android.annotation.Nullable; import android.app.Service; import android.content.Intent; import android.os.IBinder; +import java.io.FileDescriptor; +import java.io.PrintWriter; + public class FusedLocationService extends Service { - private FusedLocationProvider mProvider; + @Nullable private FusedLocationProvider mProvider; @Override public IBinder onBind(Intent intent) { if (mProvider == null) { mProvider = new FusedLocationProvider(this); - mProvider.init(); + mProvider.start(); } return mProvider.getBinder(); @@ -37,8 +41,15 @@ public class FusedLocationService extends Service { @Override public void onDestroy() { if (mProvider != null) { - mProvider.destroy(); + mProvider.stop(); mProvider = null; } } + + @Override + protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + if (mProvider != null) { + mProvider.dump(writer); + } + } } diff --git a/packages/FusedLocation/src/com/android/location/fused/FusionEngine.java b/packages/FusedLocation/src/com/android/location/fused/FusionEngine.java deleted file mode 100644 index e4610cf44636..000000000000 --- a/packages/FusedLocation/src/com/android/location/fused/FusionEngine.java +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.location.fused; - -import android.content.Context; -import android.location.Location; -import android.location.LocationListener; -import android.location.LocationManager; -import android.os.Bundle; -import android.os.Looper; -import android.os.Parcelable; -import android.os.SystemClock; -import android.util.Log; - -import com.android.location.provider.LocationProviderBase; -import com.android.location.provider.LocationRequestUnbundled; -import com.android.location.provider.ProviderRequestUnbundled; - -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.HashMap; - -public class FusionEngine implements LocationListener { - public interface Callback { - void reportLocation(Location location); - } - - private static final String TAG = "FusedLocation"; - private static final String NETWORK = LocationManager.NETWORK_PROVIDER; - private static final String GPS = LocationManager.GPS_PROVIDER; - private static final String FUSED = LocationProviderBase.FUSED_PROVIDER; - - public static final long SWITCH_ON_FRESHNESS_CLIFF_NS = 11 * 1000000000L; // 11 seconds - - private final LocationManager mLocationManager; - private final Looper mLooper; - private final Callback mCallback; - - // all fields are only used on mLooper thread. except for in dump() which is not thread-safe - private Location mFusedLocation; - private Location mGpsLocation; - private Location mNetworkLocation; - - private ProviderRequestUnbundled mRequest; - - private final HashMap<String, ProviderStats> mStats = new HashMap<>(); - - FusionEngine(Context context, Looper looper, Callback callback) { - mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); - mNetworkLocation = new Location(""); - mNetworkLocation.setAccuracy(Float.MAX_VALUE); - mGpsLocation = new Location(""); - mGpsLocation.setAccuracy(Float.MAX_VALUE); - mLooper = looper; - mCallback = callback; - - mStats.put(GPS, new ProviderStats()); - mStats.put(NETWORK, new ProviderStats()); - } - - /** Called on mLooper thread */ - public void setRequest(ProviderRequestUnbundled request) { - mRequest = request; - updateRequirements(); - } - - private static class ProviderStats { - public boolean requested; - public long requestTime; - public long minTime; - - @Override - public String toString() { - return (requested ? " REQUESTED" : " ---"); - } - } - - private void enableProvider(String name, long minTime) { - ProviderStats stats = mStats.get(name); - if (stats == null) return; - - if (mLocationManager.isProviderEnabled(name)) { - if (!stats.requested) { - stats.requestTime = SystemClock.elapsedRealtime(); - stats.requested = true; - stats.minTime = minTime; - mLocationManager.requestLocationUpdates(name, minTime, 0, this, mLooper); - } else if (stats.minTime != minTime) { - stats.minTime = minTime; - mLocationManager.requestLocationUpdates(name, minTime, 0, this, mLooper); - } - } - } - - private void disableProvider(String name) { - ProviderStats stats = mStats.get(name); - if (stats == null) return; - - if (stats.requested) { - stats.requested = false; - mLocationManager.removeUpdates(this); //TODO GLOBAL - } - } - - private void updateRequirements() { - if (mRequest == null || !mRequest.getReportLocation()) { - mRequest = null; - disableProvider(NETWORK); - disableProvider(GPS); - return; - } - - long networkInterval = Long.MAX_VALUE; - long gpsInterval = Long.MAX_VALUE; - for (LocationRequestUnbundled request : mRequest.getLocationRequests()) { - switch (request.getQuality()) { - case LocationRequestUnbundled.ACCURACY_FINE: - case LocationRequestUnbundled.POWER_HIGH: - if (request.getInterval() < gpsInterval) { - gpsInterval = request.getInterval(); - } - if (request.getInterval() < networkInterval) { - networkInterval = request.getInterval(); - } - break; - case LocationRequestUnbundled.ACCURACY_BLOCK: - case LocationRequestUnbundled.ACCURACY_CITY: - case LocationRequestUnbundled.POWER_LOW: - if (request.getInterval() < networkInterval) { - networkInterval = request.getInterval(); - } - break; - } - } - - if (gpsInterval < Long.MAX_VALUE) { - enableProvider(GPS, gpsInterval); - } else { - disableProvider(GPS); - } - if (networkInterval < Long.MAX_VALUE) { - enableProvider(NETWORK, networkInterval); - } else { - disableProvider(NETWORK); - } - } - - /** - * Test whether one location (a) is better to use than another (b). - */ - private static boolean isBetterThan(Location locationA, Location locationB) { - if (locationA == null) { - return false; - } - if (locationB == null) { - return true; - } - // A provider is better if the reading is sufficiently newer. Heading - // underground can cause GPS to stop reporting fixes. In this case it's - // appropriate to revert to cell, even when its accuracy is less. - if (locationA.getElapsedRealtimeNanos() - > locationB.getElapsedRealtimeNanos() + SWITCH_ON_FRESHNESS_CLIFF_NS) { - return true; - } - - // A provider is better if it has better accuracy. Assuming both readings - // are fresh (and by that accurate), choose the one with the smaller - // accuracy circle. - if (!locationA.hasAccuracy()) { - return false; - } - if (!locationB.hasAccuracy()) { - return true; - } - return locationA.getAccuracy() < locationB.getAccuracy(); - } - - private void updateFusedLocation() { - // may the best location win! - if (isBetterThan(mGpsLocation, mNetworkLocation)) { - mFusedLocation = new Location(mGpsLocation); - } else { - mFusedLocation = new Location(mNetworkLocation); - } - mFusedLocation.setProvider(FUSED); - if (mNetworkLocation != null) { - // copy NO_GPS_LOCATION extra from mNetworkLocation into mFusedLocation - Bundle srcExtras = mNetworkLocation.getExtras(); - if (srcExtras != null) { - Parcelable srcParcelable = - srcExtras.getParcelable(LocationProviderBase.EXTRA_NO_GPS_LOCATION); - if (srcParcelable instanceof Location) { - Bundle dstExtras = mFusedLocation.getExtras(); - if (dstExtras == null) { - dstExtras = new Bundle(); - mFusedLocation.setExtras(dstExtras); - } - dstExtras.putParcelable(LocationProviderBase.EXTRA_NO_GPS_LOCATION, - srcParcelable); - } - } - } - - if (mCallback != null) { - mCallback.reportLocation(mFusedLocation); - } else { - Log.w(TAG, "Location updates received while fusion engine not started"); - } - } - - /** Called on mLooper thread */ - @Override - public void onLocationChanged(Location location) { - if (GPS.equals(location.getProvider())) { - mGpsLocation = location; - updateFusedLocation(); - } else if (NETWORK.equals(location.getProvider())) { - mNetworkLocation = location; - updateFusedLocation(); - } - } - - /** Called on mLooper thread */ - @Override - public void onStatusChanged(String provider, int status, Bundle extras) { - } - - /** Called on mLooper thread */ - @Override - public void onProviderEnabled(String provider) { - } - - /** Called on mLooper thread */ - @Override - public void onProviderDisabled(String provider) { - } - - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - StringBuilder s = new StringBuilder(); - s.append(mRequest).append('\n'); - s.append("fused=").append(mFusedLocation).append('\n'); - s.append(String.format("gps %s\n", mGpsLocation)); - s.append(" ").append(mStats.get(GPS)).append('\n'); - s.append(String.format("net %s\n", mNetworkLocation)); - s.append(" ").append(mStats.get(NETWORK)).append('\n'); - pw.append(s); - } - - /** Called on mLooper thread */ - public void switchUser() { - // reset state to prevent location data leakage - mFusedLocation = null; - mGpsLocation = null; - mNetworkLocation = null; - } -} diff --git a/packages/FusedLocation/test/AndroidManifest.xml b/packages/FusedLocation/test/AndroidManifest.xml new file mode 100644 index 000000000000..d6c4107a6e4a --- /dev/null +++ b/packages/FusedLocation/test/AndroidManifest.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.location.fused.tests"> + + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> + <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/> + <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> + <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> + <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> + <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" /> + + <application android:label="FusedLocation Tests"> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.location.fused.tests" + android:label="FusedLocation Tests" /> +</manifest> diff --git a/packages/FusedLocation/test/AndroidTest.xml b/packages/FusedLocation/test/AndroidTest.xml new file mode 100644 index 000000000000..f88e595e9763 --- /dev/null +++ b/packages/FusedLocation/test/AndroidTest.xml @@ -0,0 +1,30 @@ +<?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. + --> +<configuration description="FusedLocation Tests"> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="test-file-name" value="FusedLocationTests.apk" /> + </target_preparer> + + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="framework-base-presubmit" /> + <option name="test-tag" value="FusedLocationTests" /> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.location.fused.tests" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false"/> + </test> +</configuration>
\ No newline at end of file diff --git a/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java new file mode 100644 index 000000000000..33126510bc53 --- /dev/null +++ b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java @@ -0,0 +1,273 @@ +/* + * 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.location.fused.tests; + +import static android.location.LocationManager.FUSED_PROVIDER; +import static android.location.LocationManager.GPS_PROVIDER; +import static android.location.LocationManager.NETWORK_PROVIDER; + +import static androidx.test.ext.truth.location.LocationSubject.assertThat; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.location.Criteria; +import android.location.Location; +import android.location.LocationManager; +import android.location.LocationRequest; +import android.os.ParcelFileDescriptor; +import android.os.SystemClock; +import android.os.WorkSource; +import android.util.Log; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.location.ILocationProvider; +import com.android.internal.location.ILocationProviderManager; +import com.android.internal.location.ProviderProperties; +import com.android.internal.location.ProviderRequest; +import com.android.location.fused.FusedLocationProvider; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +@RunWith(AndroidJUnit4.class) +public class FusedLocationServiceTest { + + private static final String TAG = "FusedLocationServiceTest"; + + private static final long TIMEOUT_MS = 5000; + + private Context mContext; + private Random mRandom; + private LocationManager mLocationManager; + + private ILocationProvider mProvider; + private LocationProviderManagerCapture mManager; + + @Before + public void setUp() throws Exception { + long seed = System.currentTimeMillis(); + Log.i(TAG, "location seed: " + seed); + + mContext = InstrumentationRegistry.getTargetContext(); + mRandom = new Random(seed); + mLocationManager = mContext.getSystemService(LocationManager.class); + + setMockLocation(true); + + mManager = new LocationProviderManagerCapture(); + mProvider = ILocationProvider.Stub.asInterface( + new FusedLocationProvider(mContext).getBinder()); + mProvider.setLocationProviderManager(mManager); + + mLocationManager.addTestProvider(NETWORK_PROVIDER, + true, + false, + true, + false, + false, + false, + false, + Criteria.POWER_MEDIUM, + Criteria.ACCURACY_FINE); + mLocationManager.setTestProviderEnabled(NETWORK_PROVIDER, true); + mLocationManager.addTestProvider(GPS_PROVIDER, + true, + false, + true, + false, + false, + false, + false, + Criteria.POWER_MEDIUM, + Criteria.ACCURACY_FINE); + mLocationManager.setTestProviderEnabled(GPS_PROVIDER, true); + } + + @After + public void tearDown() throws Exception { + for (String provider : mLocationManager.getAllProviders()) { + mLocationManager.removeTestProvider(provider); + } + + setMockLocation(false); + } + + @Test + public void testNetworkRequest() throws Exception { + LocationRequest request = LocationRequest.createFromDeprecatedProvider(FUSED_PROVIDER, 1000, + 0, false); + + mProvider.setRequest( + new ProviderRequest.Builder() + .setInterval(1000) + .setLocationRequests(Collections.singletonList(request)) + .build(), + new WorkSource()); + + Location location = createLocation(NETWORK_PROVIDER, mRandom); + mLocationManager.setTestProviderLocation(NETWORK_PROVIDER, location); + + assertThat(mManager.getNextLocation(TIMEOUT_MS)).isEqualTo(location); + } + + @Test + public void testGpsRequest() throws Exception { + LocationRequest request = LocationRequest.createFromDeprecatedProvider(FUSED_PROVIDER, 1000, + 0, false).setQuality(LocationRequest.POWER_HIGH); + + mProvider.setRequest( + new ProviderRequest.Builder() + .setInterval(1000) + .setLocationRequests(Collections.singletonList(request)) + .build(), + new WorkSource()); + + Location location = createLocation(GPS_PROVIDER, mRandom); + mLocationManager.setTestProviderLocation(GPS_PROVIDER, location); + + assertThat(mManager.getNextLocation(TIMEOUT_MS)).isEqualTo(location); + } + + @Test + public void testBypassRequest() throws Exception { + LocationRequest request = LocationRequest.createFromDeprecatedProvider(FUSED_PROVIDER, 1000, + 0, false).setQuality(LocationRequest.POWER_HIGH).setLocationSettingsIgnored(true); + + mProvider.setRequest( + new ProviderRequest.Builder() + .setInterval(1000) + .setLocationSettingsIgnored(true) + .setLocationRequests(Collections.singletonList(request)) + .build(), + new WorkSource()); + + boolean containsNetworkBypass = false; + for (LocationRequest iRequest : mLocationManager.getTestProviderCurrentRequests( + NETWORK_PROVIDER)) { + if (iRequest.isLocationSettingsIgnored()) { + containsNetworkBypass = true; + break; + } + } + + boolean containsGpsBypass = false; + for (LocationRequest iRequest : mLocationManager.getTestProviderCurrentRequests( + GPS_PROVIDER)) { + if (iRequest.isLocationSettingsIgnored()) { + containsGpsBypass = true; + break; + } + } + + assertThat(containsNetworkBypass).isTrue(); + assertThat(containsGpsBypass).isTrue(); + } + + private static class LocationProviderManagerCapture extends ILocationProviderManager.Stub { + + private final LinkedBlockingQueue<Location> mLocations; + + private LocationProviderManagerCapture() { + mLocations = new LinkedBlockingQueue<>(); + } + + @Override + public void onSetAdditionalProviderPackages(List<String> packageNames) { + + } + + @Override + public void onSetEnabled(boolean enabled) { + + } + + @Override + public void onSetProperties(ProviderProperties properties) { + + } + + @Override + public void onReportLocation(Location location) { + mLocations.add(location); + } + + public Location getNextLocation(long timeoutMs) throws InterruptedException { + return mLocations.poll(timeoutMs, TimeUnit.MILLISECONDS); + } + } + + private static final double MIN_LATITUDE = -90D; + private static final double MAX_LATITUDE = 90D; + private static final double MIN_LONGITUDE = -180D; + private static final double MAX_LONGITUDE = 180D; + + private static final float MIN_ACCURACY = 1; + private static final float MAX_ACCURACY = 100; + + private static Location createLocation(String provider, Random random) { + return createLocation(provider, + MIN_LATITUDE + random.nextDouble() * (MAX_LATITUDE - MIN_LATITUDE), + MIN_LONGITUDE + random.nextDouble() * (MAX_LONGITUDE - MIN_LONGITUDE), + MIN_ACCURACY + random.nextFloat() * (MAX_ACCURACY - MIN_ACCURACY)); + } + + private static Location createLocation(String provider, double latitude, double longitude, + float accuracy) { + Location location = new Location(provider); + location.setLatitude(latitude); + location.setLongitude(longitude); + location.setAccuracy(accuracy); + location.setTime(System.currentTimeMillis()); + location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos()); + return location; + } + + private static void setMockLocation(boolean allowed) throws IOException { + ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation().getUiAutomation() + .executeShellCommand("appops set " + + InstrumentationRegistry.getTargetContext().getPackageName() + + " android:mock_location " + (allowed ? "allow" : "deny")); + try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + byte[] buffer = new byte[32768]; + int count; + try { + while ((count = fis.read(buffer)) != -1) { + os.write(buffer, 0, count); + } + fis.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + Log.e(TAG, new String(os.toByteArray())); + } + } +} diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp index 96a98dca8a5b..d67bd8da844f 100644 --- a/packages/SettingsProvider/Android.bp +++ b/packages/SettingsProvider/Android.bp @@ -30,7 +30,7 @@ android_test { "src/com/android/providers/settings/SettingsBackupAgent.java", "src/com/android/providers/settings/SettingsState.java", "src/com/android/providers/settings/SettingsHelper.java", - "src/com/android/providers/settings/WifiSoftApBandChangedNotifier.java", + "src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java", ], static_libs: [ "androidx.test.rules", diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java index fb558abe3865..cdf97285a16f 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java @@ -968,18 +968,14 @@ public class SettingsBackupAgent extends BackupAgentHelper { } private void restoreSoftApConfiguration(byte[] data) { - SoftApConfiguration config = mWifiManager.restoreSoftApBackupData(data); - if (config != null) { - int originalApBand = config.getBand(); + SoftApConfiguration configInCloud = mWifiManager.restoreSoftApBackupData(data); + if (configInCloud != null) { if (DEBUG) Log.d(TAG, "Successfully unMarshaled SoftApConfiguration "); - - // Depending on device hardware, we may need to notify the user of a setting change for - // the apBand preference - boolean dualMode = mWifiManager.isDualModeSupported(); - int storedApBand = mWifiManager.getSoftApConfiguration().getBand(); - if (dualMode && storedApBand != originalApBand) { + // Depending on device hardware, we may need to notify the user of a setting change + SoftApConfiguration storedConfig = mWifiManager.getSoftApConfiguration(); + if (!storedConfig.equals(configInCloud)) { Log.d(TAG, "restored ap configuration requires a conversion, notify the user"); - WifiSoftApBandChangedNotifier.notifyUserOfApBandConversion(this); + WifiSoftApConfigChangedNotifier.notifyUserOfConfigConversion(this); } } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index c913999ecb7c..486386f3d284 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -62,6 +62,7 @@ import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.Process; +import android.os.RemoteCallback; import android.os.RemoteException; import android.os.SELinux; import android.os.ServiceManager; @@ -275,6 +276,9 @@ public class SettingsProvider extends ContentProvider { private final Object mLock = new Object(); @GuardedBy("mLock") + private RemoteCallback mConfigMonitorCallback; + + @GuardedBy("mLock") private SettingsRegistry mSettingsRegistry; @GuardedBy("mLock") @@ -450,8 +454,17 @@ public class SettingsProvider extends ContentProvider { case Settings.CALL_METHOD_LIST_CONFIG: { String prefix = getSettingPrefix(args); - return packageValuesForCallResult(getAllConfigFlags(prefix), + Bundle result = packageValuesForCallResult(getAllConfigFlags(prefix), isTrackingGeneration(args)); + reportDeviceConfigAccess(prefix); + return result; + } + + case Settings.CALL_METHOD_REGISTER_MONITOR_CALLBACK_CONFIG: { + RemoteCallback callback = args.getParcelable( + Settings.CALL_METHOD_MONITOR_CALLBACK_KEY); + setMonitorCallback(callback); + break; } case Settings.CALL_METHOD_LIST_GLOBAL: { @@ -1052,8 +1065,9 @@ public class SettingsProvider extends ContentProvider { enforceWritePermission(Manifest.permission.WRITE_DEVICE_CONFIG); synchronized (mLock) { - return mSettingsRegistry.setSettingsLocked(SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM, - prefix, keyValues, resolveCallingPackage()); + final int key = makeKey(SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM); + return mSettingsRegistry.setConfigSettingsLocked(key, prefix, keyValues, + resolveCallingPackage()); } } @@ -2155,6 +2169,59 @@ public class SettingsProvider extends ContentProvider { return result; } + private void setMonitorCallback(RemoteCallback callback) { + if (callback == null) { + return; + } + getContext().enforceCallingOrSelfPermission( + Manifest.permission.MONITOR_DEVICE_CONFIG_ACCESS, + "Permission denial: registering for config access requires: " + + Manifest.permission.MONITOR_DEVICE_CONFIG_ACCESS); + synchronized (mLock) { + mConfigMonitorCallback = callback; + } + } + + private void reportDeviceConfigAccess(@Nullable String prefix) { + if (prefix == null) { + return; + } + String callingPackage = getCallingPackage(); + String namespace = prefix.replace("/", ""); + if (DeviceConfig.getPublicNamespaces().contains(namespace)) { + return; + } + synchronized (mLock) { + if (mConfigMonitorCallback != null) { + Bundle callbackResult = new Bundle(); + callbackResult.putString(Settings.EXTRA_MONITOR_CALLBACK_TYPE, + Settings.EXTRA_ACCESS_CALLBACK); + callbackResult.putString(Settings.EXTRA_CALLING_PACKAGE, callingPackage); + callbackResult.putString(Settings.EXTRA_NAMESPACE, namespace); + mConfigMonitorCallback.sendResult(callbackResult); + } + } + } + + private void reportDeviceConfigUpdate(@Nullable String prefix) { + if (prefix == null) { + return; + } + String namespace = prefix.replace("/", ""); + if (DeviceConfig.getPublicNamespaces().contains(namespace)) { + return; + } + synchronized (mLock) { + if (mConfigMonitorCallback != null) { + Bundle callbackResult = new Bundle(); + callbackResult.putString(Settings.EXTRA_MONITOR_CALLBACK_TYPE, + Settings.EXTRA_NAMESPACE_UPDATED_CALLBACK); + callbackResult.putString(Settings.EXTRA_NAMESPACE, namespace); + mConfigMonitorCallback.sendResult(callbackResult); + } + } + } + private static int getRequestingUserId(Bundle args) { final int callingUserId = UserHandle.getCallingUserId(); return (args != null) ? args.getInt(Settings.CALL_METHOD_USER_KEY, callingUserId) @@ -2715,22 +2782,20 @@ public class SettingsProvider extends ContentProvider { } /** - * Set Settings using consumed keyValues, returns true if the keyValues can be set, false - * otherwise. + * Set Config Settings using consumed keyValues, returns true if the keyValues can be set, + * false otherwise. */ - public boolean setSettingsLocked(int type, int userId, String prefix, + public boolean setConfigSettingsLocked(int key, String prefix, Map<String, String> keyValues, String packageName) { - final int key = makeKey(type, userId); - SettingsState settingsState = peekSettingsStateLocked(key); if (settingsState != null) { - if (SETTINGS_TYPE_CONFIG == type && settingsState.isNewConfigBannedLocked(prefix, - keyValues)) { + if (settingsState.isNewConfigBannedLocked(prefix, keyValues)) { return false; } List<String> changedSettings = settingsState.setSettingsLocked(prefix, keyValues, packageName); if (!changedSettings.isEmpty()) { + reportDeviceConfigUpdate(prefix); notifyForConfigSettingsChangeLocked(key, prefix, changedSettings); } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApBandChangedNotifier.java b/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java index d0d4956725d4..1ee5f9093421 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApBandChangedNotifier.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java @@ -27,17 +27,17 @@ import com.android.internal.messages.nano.SystemMessageProto; import com.android.internal.notification.SystemNotificationChannels; /** - * Helper class for sending notifications when the user's Soft AP Band was changed upon restore. + * Helper class for sending notifications when the user's Soft AP config was changed upon restore. */ -public class WifiSoftApBandChangedNotifier { - private WifiSoftApBandChangedNotifier() {} +public class WifiSoftApConfigChangedNotifier { + private WifiSoftApConfigChangedNotifier() {} /** - * Send a notification informing the user that their' Soft AP Band was changed upon restore. + * Send a notification informing the user that their' Soft AP Config was changed upon restore. * When the user taps on the notification, they are taken to the Wifi Tethering page in * Settings. */ - public static void notifyUserOfApBandConversion(Context context) { + public static void notifyUserOfConfigConversion(Context context) { NotificationManager notificationManager = context.getSystemService(NotificationManager.class); diff --git a/packages/SystemUI/res/drawable/ic_screenrecord.xml b/packages/SystemUI/res/drawable/ic_screenrecord.xml new file mode 100644 index 000000000000..6d8bd0dd66fb --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_screenrecord.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" + android:tint="?android:attr/colorControlNormal"> + <path + android:pathData="M12,12m-8,0a8,8 0,1 1,16 0a8,8 0,1 1,-16 0" + android:fillColor="#FFFFFFFF"/> +</vector> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 639005ba1c40..44a7fda6bce3 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -194,6 +194,10 @@ <!-- Power menu item for taking a screenshot [CHAR LIMIT=20]--> <string name="global_action_screenshot">Screenshot</string> + <!-- text to show in place of RemoteInput images when they cannot be shown. + [CHAR LIMIT=50] --> + <string name="remote_input_image_insertion_text">Image inserted</string> + <!-- Notification ticker displayed when a screenshot is being saved to the Gallery. [CHAR LIMIT=30] --> <string name="screenshot_saving_ticker">Saving screenshot\u2026</string> <!-- Notification title displayed when a screenshot is being saved to the Gallery. [CHAR LIMIT=50] --> @@ -222,6 +226,8 @@ <string name="screenrecord_mic_label">Record voiceover</string> <!-- Label for the checkbox to enable showing location of touches during screen recording [CHAR LIMIT=NONE]--> <string name="screenrecord_taps_label">Show taps</string> + <!-- Label for notification that the user can tap to stop and save the screen recording [CHAR LIMIT=NONE] --> + <string name="screenrecord_stop_text">Tap to stop</string> <!-- Label for notification action to stop and save the screen recording [CHAR LIMIT=35] --> <string name="screenrecord_stop_label">Stop</string> <!-- Label for notification action to pause screen recording [CHAR LIMIT=35] --> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/UniversalSmartspaceUtils.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/UniversalSmartspaceUtils.java index 70a464dd254c..871cae3b4f8d 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/UniversalSmartspaceUtils.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/UniversalSmartspaceUtils.java @@ -37,7 +37,7 @@ public final class UniversalSmartspaceUtils { Intent intent = new Intent(ACTION_REQUEST_SMARTSPACE_VIEW); Bundle inputBundle = new Bundle(); - inputBundle.putBinder(BUNDLE_KEY_INPUT_TOKEN, surfaceView.getInputToken()); + inputBundle.putBinder(BUNDLE_KEY_INPUT_TOKEN, surfaceView.getHostToken()); return intent .putExtra(INTENT_KEY_SURFACE_CONTROL, surfaceView.getSurfaceControl()) .putExtra(INTENT_KEY_INPUT_BUNDLE, inputBundle) diff --git a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java index 2f8ef2dc8828..b2423b9bf252 100644 --- a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java +++ b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java @@ -172,7 +172,7 @@ public class AdminSecondaryLockScreenController { private void onSurfaceReady() { try { - mClient.onSurfaceReady(mView.getInputToken(), mCallback); + mClient.onSurfaceReady(mView.getHostToken(), mCallback); } catch (RemoteException e) { Log.e(TAG, "Error in onSurfaceReady", e); dismiss(KeyguardUpdateMonitor.getCurrentUser()); diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java index 4a5bc2a8e28e..0106609b9271 100644 --- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java +++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java @@ -39,7 +39,6 @@ import android.util.Log; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import com.android.internal.telephony.TelephonyIntents; import com.android.settingslib.WirelessUtils; import com.android.systemui.Dependency; import com.android.systemui.R; @@ -337,15 +336,15 @@ public class CarrierTextController { CharSequence text = getContext().getText(com.android.internal.R.string.emergency_calls_only); Intent i = getContext().registerReceiver(null, - new IntentFilter(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION)); + new IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED)); if (i != null) { String spn = ""; String plmn = ""; - if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false)) { - spn = i.getStringExtra(TelephonyIntents.EXTRA_SPN); + if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_SPN, false)) { + spn = i.getStringExtra(TelephonyManager.EXTRA_SPN); } - if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false)) { - plmn = i.getStringExtra(TelephonyIntents.EXTRA_PLMN); + if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_PLMN, false)) { + plmn = i.getStringExtra(TelephonyManager.EXTRA_PLMN); } if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn); if (Objects.equals(plmn, spn)) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java index 4e7956db4a2b..139f04241982 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java @@ -290,7 +290,7 @@ public class KeyguardDisplayManager { View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); - getWindow().setFitWindowInsetsTypes(0 /* types */); + getWindow().getAttributes().setFitWindowInsetsTypes(0 /* types */); getWindow().setNavigationBarContrastEnforced(false); getWindow().setNavigationBarColor(Color.TRANSPARENT); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java index 5d35169cf926..f61f585cfe7c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java @@ -140,6 +140,13 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe mLayoutTransition.setAnimateParentHierarchy(false); } + // Temporary workaround to allow KeyguardStatusView to inflate a copy for Universal Smartspace. + // Eventually the existing copy will be reparented instead, and we won't need this. + public KeyguardSliceView(Context context, AttributeSet attributeSet) { + this(context, attributeSet, Dependency.get(ActivityStarter.class), + Dependency.get(ConfigurationController.class)); + } + @Override protected void onFinishInflate() { super.onFinishInflate(); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java index 5a1c9976f021..61caf3bc5d8f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java @@ -18,10 +18,15 @@ package com.android.keyguard; import android.app.ActivityManager; import android.app.IActivityManager; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.res.Resources; import android.graphics.Color; +import android.graphics.PixelFormat; import android.os.Handler; +import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; import android.text.TextUtils; @@ -30,7 +35,11 @@ import android.util.AttributeSet; import android.util.Log; import android.util.Slog; import android.util.TypedValue; +import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; import android.view.View; +import android.view.WindowManager; +import android.view.WindowlessWindowManager; import android.widget.GridLayout; import android.widget.LinearLayout; import android.widget.TextView; @@ -40,6 +49,7 @@ import androidx.core.graphics.ColorUtils; import com.android.internal.widget.LockPatternUtils; import com.android.systemui.Dependency; import com.android.systemui.R; +import com.android.systemui.shared.system.UniversalSmartspaceUtils; import com.android.systemui.statusbar.policy.ConfigurationController; import java.io.FileDescriptor; @@ -76,6 +86,7 @@ public class KeyguardStatusView extends GridLayout implements private int mIconTopMargin; private int mIconTopMarginWithHeader; private boolean mShowingHeader; + private SurfaceControlViewHost mUniversalSmartspaceViewHost; private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { @@ -122,6 +133,38 @@ public class KeyguardStatusView extends GridLayout implements } }; + private BroadcastReceiver mUniversalSmartspaceBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent i) { + // TODO(b/148159743): Restrict to Pixel Launcher. + if (UniversalSmartspaceUtils.ACTION_REQUEST_SMARTSPACE_VIEW.equals(i.getAction())) { + if (mUniversalSmartspaceViewHost != null) { + mUniversalSmartspaceViewHost.die(); + } + SurfaceControl surfaceControl = UniversalSmartspaceUtils.getSurfaceControl(i); + if (surfaceControl != null) { + IBinder input = UniversalSmartspaceUtils.getInputToken(i); + + WindowlessWindowManager windowlessWindowManager = + new WindowlessWindowManager(context.getResources().getConfiguration(), + surfaceControl, input); + mUniversalSmartspaceViewHost = new SurfaceControlViewHost(context, + context.getDisplay(), windowlessWindowManager); + WindowManager.LayoutParams layoutParams = + new WindowManager.LayoutParams( + surfaceControl.getWidth(), + surfaceControl.getHeight(), + WindowManager.LayoutParams.TYPE_APPLICATION, + 0, + PixelFormat.TRANSPARENT); + + mUniversalSmartspaceViewHost.addView( + inflate(context, R.layout.keyguard_status_area, null), layoutParams); + } + } + } + };; + public KeyguardStatusView(Context context) { this(context, null, 0); } @@ -316,6 +359,8 @@ public class KeyguardStatusView extends GridLayout implements super.onAttachedToWindow(); Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mInfoCallback); Dependency.get(ConfigurationController.class).addCallback(this); + getContext().registerReceiver(mUniversalSmartspaceBroadcastReceiver, + new IntentFilter(UniversalSmartspaceUtils.ACTION_REQUEST_SMARTSPACE_VIEW)); } @Override @@ -323,6 +368,7 @@ public class KeyguardStatusView extends GridLayout implements super.onDetachedFromWindow(); Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mInfoCallback); Dependency.get(ConfigurationController.class).removeCallback(this); + getContext().unregisterReceiver(mUniversalSmartspaceBroadcastReceiver); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index f03648ad096d..f51cd83bb2d1 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -1089,7 +1089,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab // keep compatibility with apps that aren't direct boot aware. // SysUI should just ignore this broadcast because it was already received // and processed previously. - if (intent.getBooleanExtra(TelephonyIntents.EXTRA_REBROADCAST_ON_UNLOCK, false)) { + if (intent.getBooleanExtra(Intent.EXTRA_REBROADCAST_ON_UNLOCK, false)) { // Guarantee mTelephonyCapable state after SysUI crash and restart if (args.simState == TelephonyManager.SIM_STATE_ABSENT) { mHandler.obtainMessage(MSG_TELEPHONY_CAPABLE, true).sendToTarget(); diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 37351985b3bd..40f98005aff4 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -1577,7 +1577,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY); - window.setFitWindowInsetsTypes(0 /* types */); + window.getAttributes().setFitWindowInsetsTypes(0 /* types */); setTitle(R.string.global_actions); mPanelController = plugin; diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java index c911bf28effd..ffc9e5ac68e6 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java @@ -126,7 +126,7 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks, window.getAttributes().height = ViewGroup.LayoutParams.MATCH_PARENT; window.getAttributes().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY); - window.setFitWindowInsetsTypes(0 /* types */); + window.getAttributes().setFitWindowInsetsTypes(0 /* types */); window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); window.addFlags( WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java index f30c181b3c99..79a7df24e217 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java @@ -111,7 +111,9 @@ public class TileQueryHelper { final ArrayList<QSTile> tilesToAdd = new ArrayList<>(); for (String spec : possibleTiles) { - // Only add current and stock tiles that can be created from QSFactoryImpl + // Only add current and stock tiles that can be created from QSFactoryImpl. + // Do not include CustomTile. Those will be created by `addPackageTiles`. + if (spec.startsWith(CustomTile.PREFIX)) continue; final QSTile tile = host.createTile(spec); if (tile == null) { continue; diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java index 1b321685e88b..b091ad8c0db8 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java @@ -34,6 +34,7 @@ import android.media.MediaRecorder; import android.media.projection.MediaProjection; import android.media.projection.MediaProjectionManager; import android.net.Uri; +import android.os.Bundle; import android.os.IBinder; import android.provider.MediaStore; import android.provider.Settings; @@ -72,9 +73,6 @@ public class RecordingService extends Service { private static final String ACTION_START = "com.android.systemui.screenrecord.START"; private static final String ACTION_STOP = "com.android.systemui.screenrecord.STOP"; - private static final String ACTION_PAUSE = "com.android.systemui.screenrecord.PAUSE"; - private static final String ACTION_RESUME = "com.android.systemui.screenrecord.RESUME"; - private static final String ACTION_CANCEL = "com.android.systemui.screenrecord.CANCEL"; private static final String ACTION_SHARE = "com.android.systemui.screenrecord.SHARE"; private static final String ACTION_DELETE = "com.android.systemui.screenrecord.DELETE"; @@ -94,6 +92,7 @@ public class RecordingService extends Service { private boolean mUseAudio; private boolean mShowTaps; + private boolean mOriginalShowTaps; private File mTempFile; @Inject @@ -145,38 +144,11 @@ public class RecordingService extends Service { } break; - case ACTION_CANCEL: - stopRecording(); - - // Delete temp file - if (!mTempFile.delete()) { - Log.e(TAG, "Error canceling screen recording!"); - Toast.makeText(this, R.string.screenrecord_delete_error, Toast.LENGTH_LONG) - .show(); - } else { - Toast.makeText(this, R.string.screenrecord_cancel_success, Toast.LENGTH_LONG) - .show(); - } - - // Close quick shade - sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); - break; - case ACTION_STOP: stopRecording(); saveRecording(notificationManager); break; - case ACTION_PAUSE: - mMediaRecorder.pause(); - setNotificationActions(true, notificationManager); - break; - - case ACTION_RESUME: - mMediaRecorder.resume(); - setNotificationActions(false, notificationManager); - break; - case ACTION_SHARE: Uri shareUri = Uri.parse(intent.getStringExtra(EXTRA_PATH)); @@ -233,9 +205,14 @@ public class RecordingService extends Service { */ private void startRecording() { try { - mTempFile = File.createTempFile("temp", ".mp4"); + File cacheDir = getCacheDir(); + cacheDir.mkdirs(); + mTempFile = File.createTempFile("temp", ".mp4", cacheDir); Log.d(TAG, "Writing video output to: " + mTempFile.getAbsolutePath()); + mOriginalShowTaps = 1 == Settings.System.getInt( + getApplicationContext().getContentResolver(), + Settings.System.SHOW_TOUCHES, 0); setTapsVisible(mShowTaps); // Set up media recorder @@ -295,50 +272,33 @@ public class RecordingService extends Service { NotificationChannel channel = new NotificationChannel( CHANNEL_ID, getString(R.string.screenrecord_name), - NotificationManager.IMPORTANCE_LOW); + NotificationManager.IMPORTANCE_DEFAULT); channel.setDescription(getString(R.string.screenrecord_channel_description)); channel.enableVibration(true); NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.createNotificationChannel(channel); + Bundle extras = new Bundle(); + extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, + getResources().getString(R.string.screenrecord_name)); + mRecordingNotificationBuilder = new Notification.Builder(this, CHANNEL_ID) - .setSmallIcon(R.drawable.ic_android) + .setSmallIcon(R.drawable.ic_screenrecord) .setContentTitle(getResources().getString(R.string.screenrecord_name)) + .setContentText(getResources().getString(R.string.screenrecord_stop_text)) .setUsesChronometer(true) - .setOngoing(true); - setNotificationActions(false, notificationManager); - Notification notification = mRecordingNotificationBuilder.build(); - startForeground(NOTIFICATION_ID, notification); - } - - private void setNotificationActions(boolean isPaused, NotificationManager notificationManager) { - String pauseString = getResources() - .getString(isPaused ? R.string.screenrecord_resume_label - : R.string.screenrecord_pause_label); - Intent pauseIntent = isPaused ? getResumeIntent(this) : getPauseIntent(this); - - mRecordingNotificationBuilder.setActions( - new Notification.Action.Builder( - Icon.createWithResource(this, R.drawable.ic_android), - getResources().getString(R.string.screenrecord_stop_label), - PendingIntent - .getService(this, REQUEST_CODE, getStopIntent(this), - PendingIntent.FLAG_UPDATE_CURRENT)) - .build(), - new Notification.Action.Builder( - Icon.createWithResource(this, R.drawable.ic_android), pauseString, - PendingIntent.getService(this, REQUEST_CODE, pauseIntent, + .setColorized(true) + .setColor(getResources().getColor(R.color.GM2_red_700)) + .setOngoing(true) + .setContentIntent( + PendingIntent.getService( + this, REQUEST_CODE, getStopIntent(this), PendingIntent.FLAG_UPDATE_CURRENT)) - .build(), - new Notification.Action.Builder( - Icon.createWithResource(this, R.drawable.ic_android), - getResources().getString(R.string.screenrecord_cancel_label), - PendingIntent - .getService(this, REQUEST_CODE, getCancelIntent(this), - PendingIntent.FLAG_UPDATE_CURRENT)) - .build()); + .addExtras(extras); notificationManager.notify(NOTIFICATION_ID, mRecordingNotificationBuilder.build()); + Notification notification = mRecordingNotificationBuilder.build(); + startForeground(NOTIFICATION_ID, notification); } private Notification createSaveNotification(Uri uri) { @@ -347,7 +307,7 @@ public class RecordingService extends Service { .setDataAndType(uri, "video/mp4"); Notification.Action shareAction = new Notification.Action.Builder( - Icon.createWithResource(this, R.drawable.ic_android), + Icon.createWithResource(this, R.drawable.ic_screenrecord), getResources().getString(R.string.screenrecord_share_label), PendingIntent.getService( this, @@ -357,7 +317,7 @@ public class RecordingService extends Service { .build(); Notification.Action deleteAction = new Notification.Action.Builder( - Icon.createWithResource(this, R.drawable.ic_android), + Icon.createWithResource(this, R.drawable.ic_screenrecord), getResources().getString(R.string.screenrecord_delete_label), PendingIntent.getService( this, @@ -366,8 +326,12 @@ public class RecordingService extends Service { PendingIntent.FLAG_UPDATE_CURRENT)) .build(); + Bundle extras = new Bundle(); + extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, + getResources().getString(R.string.screenrecord_name)); + Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID) - .setSmallIcon(R.drawable.ic_android) + .setSmallIcon(R.drawable.ic_screenrecord) .setContentTitle(getResources().getString(R.string.screenrecord_name)) .setContentText(getResources().getString(R.string.screenrecord_save_message)) .setContentIntent(PendingIntent.getActivity( @@ -377,7 +341,8 @@ public class RecordingService extends Service { Intent.FLAG_GRANT_READ_URI_PERMISSION)) .addAction(shareAction) .addAction(deleteAction) - .setAutoCancel(true); + .setAutoCancel(true) + .addExtras(extras); // Add thumbnail if available Bitmap thumbnailBitmap = null; @@ -400,7 +365,7 @@ public class RecordingService extends Service { } private void stopRecording() { - setTapsVisible(false); + setTapsVisible(mOriginalShowTaps); mMediaRecorder.stop(); mMediaRecorder.release(); mMediaRecorder = null; @@ -459,18 +424,6 @@ public class RecordingService extends Service { return new Intent(context, RecordingService.class).setAction(ACTION_STOP); } - private static Intent getPauseIntent(Context context) { - return new Intent(context, RecordingService.class).setAction(ACTION_PAUSE); - } - - private static Intent getResumeIntent(Context context) { - return new Intent(context, RecordingService.class).setAction(ACTION_RESUME); - } - - private static Intent getCancelIntent(Context context) { - return new Intent(context, RecordingService.class).setAction(ACTION_CANCEL); - } - private static Intent getShareIntent(Context context, String path) { return new Intent(context, RecordingService.class).setAction(ACTION_SHARE) .putExtra(EXTRA_PATH, path); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt index 083fbc92dc68..ab69d477c2ee 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt @@ -22,6 +22,7 @@ import android.os.SystemProperties import android.util.MathUtils import android.view.SurfaceControl import android.view.ViewRootImpl +import androidx.annotation.VisibleForTesting import com.android.internal.util.IndentingPrintWriter import com.android.systemui.DumpController import com.android.systemui.Dumpable @@ -33,7 +34,7 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton -class BlurUtils @Inject constructor( +open class BlurUtils @Inject constructor( @Main private val resources: Resources, val dumpController: DumpController ) : Dumpable { @@ -63,22 +64,28 @@ class BlurUtils @Inject constructor( * @param radius blur radius in pixels. */ fun applyBlur(viewRootImpl: ViewRootImpl?, radius: Int) { - if (viewRootImpl == null || !supportsBlursOnWindows()) { + if (viewRootImpl == null || !viewRootImpl.surfaceControl.isValid || + !supportsBlursOnWindows()) { return } - SurfaceControl.Transaction().use { + createTransaction().use { it.setBackgroundBlurRadius(viewRootImpl.surfaceControl, radius) it.apply() } } + @VisibleForTesting + open fun createTransaction(): SurfaceControl.Transaction { + return SurfaceControl.Transaction() + } + /** * If this device can render blurs. * * @see android.view.SurfaceControl.Transaction#setBackgroundBlurRadius(SurfaceControl, int) * @return {@code true} when supported. */ - fun supportsBlursOnWindows(): Boolean { + open fun supportsBlursOnWindows(): Boolean { return blurSysProp && ActivityManager.isHighEndGfx() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java index 8cc45f2e17d4..4d6764e0a29e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java @@ -195,6 +195,7 @@ public class NotificationListener extends NotificationListenerWithPlugins { new ArrayList<>(), new ArrayList<>(), false, + false, false ); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java index 667e721ae37d..f3783c83a301 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -25,8 +25,10 @@ import android.app.KeyguardManager; import android.app.Notification; import android.app.PendingIntent; import android.app.RemoteInput; +import android.app.RemoteInputHistoryItem; import android.content.Context; import android.content.Intent; +import android.net.Uri; import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; @@ -345,7 +347,8 @@ public class NotificationRemoteInputManager implements Dumpable { }); mSmartReplyController.setCallback((entry, reply) -> { StatusBarNotification newSbn = - rebuildNotificationWithRemoteInput(entry, reply, true /* showSpinner */); + rebuildNotificationWithRemoteInput(entry, reply, true /* showSpinner */, + null /* mimeType */, null /* uri */); mEntryManager.updateNotification(newSbn, null /* ranking */); }); } @@ -527,28 +530,36 @@ public class NotificationRemoteInputManager implements Dumpable { StatusBarNotification rebuildNotificationForCanceledSmartReplies( NotificationEntry entry) { return rebuildNotificationWithRemoteInput(entry, null /* remoteInputTest */, - false /* showSpinner */); + false /* showSpinner */, null /* mimeType */, null /* uri */); } @VisibleForTesting StatusBarNotification rebuildNotificationWithRemoteInput(NotificationEntry entry, - CharSequence remoteInputText, boolean showSpinner) { + CharSequence remoteInputText, boolean showSpinner, String mimeType, Uri uri) { StatusBarNotification sbn = entry.getSbn(); Notification.Builder b = Notification.Builder .recoverBuilder(mContext, sbn.getNotification().clone()); - if (remoteInputText != null) { - CharSequence[] oldHistory = sbn.getNotification().extras - .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY); - CharSequence[] newHistory; - if (oldHistory == null) { - newHistory = new CharSequence[1]; + if (remoteInputText != null || uri != null) { + RemoteInputHistoryItem[] oldHistoryItems = (RemoteInputHistoryItem[]) + sbn.getNotification().extras.getParcelableArray( + Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); + RemoteInputHistoryItem[] newHistoryItems; + + if (oldHistoryItems == null) { + newHistoryItems = new RemoteInputHistoryItem[1]; } else { - newHistory = new CharSequence[oldHistory.length + 1]; - System.arraycopy(oldHistory, 0, newHistory, 1, oldHistory.length); + newHistoryItems = new RemoteInputHistoryItem[oldHistoryItems.length + 1]; + System.arraycopy(oldHistoryItems, 0, newHistoryItems, 1, oldHistoryItems.length); } - newHistory[0] = String.valueOf(remoteInputText); - b.setRemoteInputHistory(newHistory); + RemoteInputHistoryItem newItem; + if (uri != null) { + newItem = new RemoteInputHistoryItem(mimeType, uri, remoteInputText); + } else { + newItem = new RemoteInputHistoryItem(remoteInputText); + } + newHistoryItems[0] = newItem; + b.setRemoteInputHistory(newHistoryItems); } b.setShowRemoteInputSpinner(showSpinner); b.setHideSmartReplies(true); @@ -631,8 +642,11 @@ public class NotificationRemoteInputManager implements Dumpable { if (TextUtils.isEmpty(remoteInputText)) { remoteInputText = entry.remoteInputTextWhenReset; } + String remoteInputMimeType = entry.remoteInputMimeType; + Uri remoteInputUri = entry.remoteInputUri; StatusBarNotification newSbn = rebuildNotificationWithRemoteInput(entry, - remoteInputText, false /* showSpinner */); + remoteInputText, false /* showSpinner */, remoteInputMimeType, + remoteInputUri); entry.onRemoteInputInserted(); if (newSbn == null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 2fcfb8c811aa..1f77ec229041 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -37,8 +37,10 @@ import android.app.Notification.MessagingStyle.Message; import android.app.NotificationChannel; import android.app.NotificationManager.Policy; import android.app.Person; +import android.app.RemoteInputHistoryItem; import android.content.Context; import android.graphics.drawable.Icon; +import android.net.Uri; import android.os.Bundle; import android.os.SystemClock; import android.service.notification.NotificationListenerService.Ranking; @@ -120,6 +122,8 @@ public final class NotificationEntry extends ListEntry { public int targetSdk; private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET; public CharSequence remoteInputText; + public String remoteInputMimeType; + public Uri remoteInputUri; private Notification.BubbleMetadata mBubbleMetadata; /** @@ -595,8 +599,8 @@ public final class NotificationEntry extends ListEntry { return false; } Bundle extras = mSbn.getNotification().extras; - CharSequence[] replyTexts = extras.getCharSequenceArray( - Notification.EXTRA_REMOTE_INPUT_HISTORY); + RemoteInputHistoryItem[] replyTexts = (RemoteInputHistoryItem[]) extras.getParcelableArray( + Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); if (!ArrayUtils.isEmpty(replyTexts)) { return true; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt index f7fe064724a9..1eeeab3e93cb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.notification.collection -import android.app.NotificationChannel import android.app.NotificationManager.IMPORTANCE_HIGH import android.app.NotificationManager.IMPORTANCE_MIN import android.service.notification.NotificationListenerService.Ranking @@ -192,9 +191,7 @@ open class NotificationRankingManager @Inject constructor( } private fun NotificationEntry.isPeopleNotification() = - sbn.isPeopleNotification(channel) - private fun StatusBarNotification.isPeopleNotification(channel: NotificationChannel) = - peopleNotificationIdentifier.isPeopleNotification(this, channel) + peopleNotificationIdentifier.isPeopleNotification(sbn, ranking) private fun NotificationEntry.isHighPriority() = highPriorityProvider.isHighPriority(this) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java index ccd7fa3c6837..b49611688bc7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java @@ -100,7 +100,7 @@ public class HighPriorityProvider { private boolean isPeopleNotification(NotificationEntry entry) { return mPeopleNotificationIdentifier.isPeopleNotification( - entry.getSbn(), entry.getChannel()); + entry.getSbn(), entry.getRanking()); } private boolean hasUserSetImportance(NotificationEntry entry) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt index 5c90211ca6ad..4672de046c49 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt @@ -16,31 +16,20 @@ package com.android.systemui.statusbar.notification.people -import android.app.Notification -import android.content.Context -import android.app.NotificationChannel +import android.service.notification.NotificationListenerService.Ranking import android.service.notification.StatusBarNotification -import android.util.FeatureFlagUtils import javax.inject.Inject import javax.inject.Singleton interface PeopleNotificationIdentifier { - fun isPeopleNotification(sbn: StatusBarNotification, channel: NotificationChannel): Boolean + fun isPeopleNotification(sbn: StatusBarNotification, ranking: Ranking): Boolean } @Singleton class PeopleNotificationIdentifierImpl @Inject constructor( - private val personExtractor: NotificationPersonExtractor, - private val context: Context + private val personExtractor: NotificationPersonExtractor ) : PeopleNotificationIdentifier { - override fun isPeopleNotification(sbn: StatusBarNotification, channel: NotificationChannel) = - ((sbn.notification.notificationStyle == Notification.MessagingStyle::class.java && - (sbn.notification.shortcutId != null || - FeatureFlagUtils.isEnabled( - context, - FeatureFlagUtils.NOTIF_CONVO_BYPASS_SHORTCUT_REQ - ))) || - personExtractor.isPersonNotification(sbn)) && - !channel.isDemoted + override fun isPeopleNotification(sbn: StatusBarNotification, ranking: Ranking) = + ranking.isConversation || personExtractor.isPersonNotification(sbn) }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt index 315ea0a49bb7..9ab3dbc8ab93 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt @@ -35,6 +35,7 @@ import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.view.Window import android.view.WindowInsets.Type +import android.view.WindowInsets.Type.statusBars import android.view.WindowManager import android.widget.TextView import com.android.internal.annotations.VisibleForTesting @@ -288,13 +289,13 @@ class ChannelEditorDialogController @Inject constructor( setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) addFlags(wmFlags) setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL) - setFitWindowInsetsTypes(getFitWindowInsetsTypes() and Type.statusBars().inv()) setWindowAnimations(com.android.internal.R.style.Animation_InputMethod) attributes = attributes.apply { format = PixelFormat.TRANSLUCENT title = ChannelEditorDialogController::class.java.simpleName gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL + fitWindowInsetsTypes = attributes.fitWindowInsetsTypes and statusBars().inv() width = MATCH_PARENT height = WRAP_CONTENT } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java index a3e13053d169..fa4bc2aba21a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java @@ -80,7 +80,15 @@ public class NotificationInlineImageResolver implements ImageResolver { public Drawable loadImage(Uri uri) { Drawable result = null; try { - result = hasCache() ? mImageCache.get(uri) : resolveImage(uri); + if (hasCache()) { + // if the uri isn't currently cached, try caching it first + if (!mImageCache.hasEntry(uri)) { + mImageCache.preload((uri)); + } + result = mImageCache.get(uri); + } else { + result = resolveImage(uri); + } } catch (IOException | SecurityException ex) { Log.d(TAG, "loadImage: Can't load image from " + uri, ex); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java index 00e38f814dff..7558022df96d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -616,8 +616,7 @@ public class PhoneStatusBarPolicy break; case TelephonyIntents.ACTION_SIM_STATE_CHANGED: // Avoid rebroadcast because SysUI is direct boot aware. - if (intent.getBooleanExtra(TelephonyIntents.EXTRA_REBROADCAST_ON_UNLOCK, - false)) { + if (intent.getBooleanExtra(Intent.EXTRA_REBROADCAST_ON_UNLOCK, false)) { break; } updateSimState(intent); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java index 28b6c389d4df..9655535dbecd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java @@ -106,8 +106,8 @@ public class SystemUIDialog extends AlertDialog { if (Dependency.get(KeyguardStateController.class).isShowing()) { final Window window = dialog.getWindow(); window.setType(LayoutParams.TYPE_STATUS_BAR_PANEL); - window.setFitWindowInsetsTypes( - window.getFitWindowInsetsTypes() & ~Type.statusBars()); + window.getAttributes().setFitWindowInsetsTypes( + window.getAttributes().getFitWindowInsetsTypes() & ~Type.statusBars()); } else { dialog.getWindow().setType(LayoutParams.TYPE_STATUS_BAR_SUB_PANEL); } @@ -118,8 +118,8 @@ public class SystemUIDialog extends AlertDialog { window.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL); window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); - window.setFitWindowInsetsTypes( - window.getFitWindowInsetsTypes() & ~Type.statusBars()); + window.getAttributes().setFitWindowInsetsTypes( + window.getAttributes().getFitWindowInsetsTypes() & ~Type.statusBars()); return dialog; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java index e675a7f373b6..5dc91047770d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java @@ -29,7 +29,6 @@ import android.text.TextUtils; import android.util.AttributeSet; import android.widget.TextView; -import com.android.internal.telephony.TelephonyIntents; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.Dependency; @@ -137,9 +136,9 @@ public class EmergencyCryptkeeperText extends TextView { displayText = getContext().getText( com.android.internal.R.string.emergency_calls_only); Intent i = getContext().registerReceiver(null, - new IntentFilter(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION)); + new IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED)); if (i != null) { - displayText = i.getStringExtra(TelephonyIntents.EXTRA_PLMN); + displayText = i.getStringExtra(TelephonyManager.EXTRA_PLMN); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java index cca100fd48bd..3a1feaa2de14 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java @@ -27,6 +27,7 @@ import android.os.Looper; import android.os.Message; import android.provider.Settings.Global; import android.telephony.Annotation; +import android.telephony.CdmaEriInformation; import android.telephony.CellSignalStrength; import android.telephony.CellSignalStrengthCdma; import android.telephony.NetworkRegistrationInfo; @@ -40,8 +41,6 @@ import android.text.TextUtils; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.telephony.TelephonyIntents; -import com.android.internal.telephony.cdma.EriInfo; import com.android.settingslib.Utils; import com.android.settingslib.graph.SignalDrawable; import com.android.settingslib.net.SignalStrengthUtil; @@ -415,10 +414,10 @@ public class MobileSignalController extends SignalController< return false; } if (isCdma() && mServiceState != null) { - final int iconMode = mServiceState.getCdmaEriIconMode(); - return mServiceState.getCdmaEriIconIndex() != EriInfo.ROAMING_INDICATOR_OFF - && (iconMode == EriInfo.ROAMING_ICON_MODE_NORMAL - || iconMode == EriInfo.ROAMING_ICON_MODE_FLASH); + final int iconMode = mPhone.getCdmaEriInformation().getEriIconMode(); + return mPhone.getCdmaEriInformation().getEriIconIndex() != CdmaEriInformation.ERI_OFF + && (iconMode == CdmaEriInformation.ERI_ICON_MODE_NORMAL + || iconMode == CdmaEriInformation.ERI_ICON_MODE_FLASH); } else { return mServiceState != null && mServiceState.getRoaming(); } @@ -430,12 +429,12 @@ public class MobileSignalController extends SignalController< public void handleBroadcast(Intent intent) { String action = intent.getAction(); - if (action.equals(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION)) { - updateNetworkName(intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false), - intent.getStringExtra(TelephonyIntents.EXTRA_SPN), - intent.getStringExtra(TelephonyIntents.EXTRA_DATA_SPN), - intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false), - intent.getStringExtra(TelephonyIntents.EXTRA_PLMN)); + if (action.equals(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED)) { + updateNetworkName(intent.getBooleanExtra(TelephonyManager.EXTRA_SHOW_SPN, false), + intent.getStringExtra(TelephonyManager.EXTRA_SPN), + intent.getStringExtra(TelephonyManager.EXTRA_DATA_SPN), + intent.getBooleanExtra(TelephonyManager.EXTRA_SHOW_PLMN, false), + intent.getStringExtra(TelephonyManager.EXTRA_PLMN)); notifyListenersIfNecessary(); } else if (action.equals(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) { updateDataSim(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index 6b3c5dc228bc..50052b8482f5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -319,7 +319,7 @@ public class NetworkControllerImpl extends BroadcastReceiver filter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED); filter.addAction(TelephonyManager.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED); filter.addAction(Intent.ACTION_SERVICE_STATE); - filter.addAction(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION); + filter.addAction(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED); filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); filter.addAction(ConnectivityManager.INET_CONDITION_ACTION); filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); @@ -526,7 +526,7 @@ public class NetworkControllerImpl extends BroadcastReceiver break; case TelephonyIntents.ACTION_SIM_STATE_CHANGED: // Avoid rebroadcast because SysUI is direct boot aware. - if (intent.getBooleanExtra(TelephonyIntents.EXTRA_REBROADCAST_ON_UNLOCK, false)) { + if (intent.getBooleanExtra(Intent.EXTRA_REBROADCAST_ON_UNLOCK, false)) { break; } // Might have different subscriptions now. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java index 307e3bcbaba5..408b3a619ff1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -161,6 +161,11 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND); RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent, results); + + mEntry.remoteInputText = mEditText.getText(); + mEntry.remoteInputUri = null; + mEntry.remoteInputMimeType = null; + if (mEntry.editedSuggestionInfo == null) { RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_FREE_FORM_INPUT); } else { @@ -177,6 +182,10 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND); RemoteInput.addDataResultToIntent(mRemoteInput, fillInIntent, results); + mEntry.remoteInputText = mContext.getString(R.string.remote_input_image_insertion_text); + mEntry.remoteInputMimeType = contentType; + mEntry.remoteInputUri = data; + return fillInIntent; } @@ -184,7 +193,6 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene mEditText.setEnabled(false); mSendButton.setVisibility(INVISIBLE); mProgressBar.setVisibility(VISIBLE); - mEntry.remoteInputText = mEditText.getText(); mEntry.lastRemoteInputSent = SystemClock.elapsedRealtime(); mController.addSpinning(mEntry.getKey(), mToken); mController.removeRemoteInput(mEntry, mToken); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index b3c2ba3a32fb..59eb6c59a2cd 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -170,7 +170,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { Assert.assertTrue("onSimStateChanged not called", mKeyguardUpdateMonitor.hasSimStateJustChanged()); - intent.putExtra(TelephonyIntents.EXTRA_REBROADCAST_ON_UNLOCK, true); + intent.putExtra(Intent.EXTRA_REBROADCAST_ON_UNLOCK, true); mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext(), intent); mTestableLooper.processAllMessages(); Assert.assertFalse("onSimStateChanged should have been skipped", diff --git a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java index 2ecc8ea8400c..b3071f957fdb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java +++ b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java @@ -19,8 +19,11 @@ import static org.mockito.Mockito.mock; import android.content.Context; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Log; public class TestableDependency extends Dependency { + private static final String TAG = "TestableDependency"; + private final ArrayMap<Object, Object> mObjs = new ArrayMap<>(); private final ArraySet<Object> mInstantiatedObjects = new ArraySet<>(); @@ -44,7 +47,7 @@ public class TestableDependency extends Dependency { public <T> void injectTestDependency(Class<T> key, T obj) { if (mInstantiatedObjects.contains(key)) { - throw new IllegalStateException(key + " was already initialized"); + Log.d(TAG, key + " was already initialized but overriding with testDependency."); } mObjs.put(key, obj); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java index 8a9a7a28efb7..4c681023175b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.any; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -77,12 +78,14 @@ public class TileQueryHelperTest extends SysuiTestCase { private static final Set<String> FACTORY_TILES = new ArraySet<>(); private static final String TEST_PKG = "test_pkg"; private static final String TEST_CLS = "test_cls"; + private static final String CUSTOM_TILE = "custom(" + TEST_PKG + "/" + TEST_CLS + ")"; static { FACTORY_TILES.addAll(Arrays.asList( new String[]{"wifi", "bt", "cell", "dnd", "inversion", "airplane", "work", "rotation", "flashlight", "location", "cast", "hotspot", "user", "battery", "saver", "night", "nfc"})); + FACTORY_TILES.add(CUSTOM_TILE); } @Mock @@ -223,6 +226,15 @@ public class TileQueryHelperTest extends SysuiTestCase { } @Test + public void testCustomTileNotCreated() { + Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES, + CUSTOM_TILE); + mTileQueryHelper.queryTiles(mQSTileHost); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); + verify(mQSTileHost, never()).createTile(CUSTOM_TILE); + } + + @Test public void testThirdPartyTilesInactive() { ResolveInfo resolveInfo = new ResolveInfo(); ServiceInfo serviceInfo = mock(ServiceInfo.class, Answers.RETURNS_MOCKS); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/BlurUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/BlurUtilsTest.kt new file mode 100644 index 000000000000..c180a889a7ec --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/BlurUtilsTest.kt @@ -0,0 +1,82 @@ +/* + * 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 + +import android.content.res.Resources +import android.view.SurfaceControl +import android.view.ViewRootImpl +import androidx.test.filters.SmallTest +import com.android.systemui.DumpController +import com.android.systemui.SysuiTestCase +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.Mockito.eq +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` +import org.mockito.MockitoAnnotations + +@SmallTest +class BlurUtilsTest : SysuiTestCase() { + + @Mock lateinit var resources: Resources + @Mock lateinit var dumpController: DumpController + @Mock lateinit var transaction: SurfaceControl.Transaction + lateinit var blurUtils: BlurUtils + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + blurUtils = TestableBlurUtils() + } + + @Test + fun testApplyBlur_noViewRoot_doesntCrash() { + blurUtils.applyBlur(null /* viewRootImple */, 10 /* radius */) + } + + @Test + fun testApplyBlur_invalidSurfaceControl() { + val surfaceControl = mock(SurfaceControl::class.java) + val viewRootImpl = mock(ViewRootImpl::class.java) + `when`(viewRootImpl.surfaceControl).thenReturn(surfaceControl) + blurUtils.applyBlur(viewRootImpl, 10 /* radius */) + } + + @Test + fun testApplyBlur_success() { + val radius = 10 + val surfaceControl = mock(SurfaceControl::class.java) + val viewRootImpl = mock(ViewRootImpl::class.java) + `when`(viewRootImpl.surfaceControl).thenReturn(surfaceControl) + `when`(surfaceControl.isValid).thenReturn(true) + blurUtils.applyBlur(viewRootImpl, radius) + verify(transaction).setBackgroundBlurRadius(eq(surfaceControl), eq(radius)) + verify(transaction).apply() + } + + inner class TestableBlurUtils() : BlurUtils(resources, dumpController) { + override fun supportsBlursOnWindows(): Boolean { + return true + } + + override fun createTransaction(): SurfaceControl.Transaction { + return transaction + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java index 64b10c816da7..1117646c482b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java @@ -10,7 +10,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Notification; +import android.app.RemoteInputHistoryItem; import android.content.Context; +import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.os.SystemClock; @@ -150,13 +152,30 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { } @Test + public void testRebuildWithRemoteInput_noExistingInput_image() { + Uri uri = mock(Uri.class); + String mimeType = "image/jpeg"; + String text = "image inserted"; + StatusBarNotification newSbn = + mRemoteInputManager.rebuildNotificationWithRemoteInput( + mEntry, text, false, mimeType, uri); + RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification() + .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); + assertEquals(1, messages.length); + assertEquals(text, messages[0].getText()); + assertEquals(mimeType, messages[0].getMimeType()); + assertEquals(uri, messages[0].getUri()); + } + + @Test public void testRebuildWithRemoteInput_noExistingInputNoSpinner() { StatusBarNotification newSbn = - mRemoteInputManager.rebuildNotificationWithRemoteInput(mEntry, "A Reply", false); - CharSequence[] messages = newSbn.getNotification().extras - .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY); + mRemoteInputManager.rebuildNotificationWithRemoteInput( + mEntry, "A Reply", false, null, null); + RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification() + .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); assertEquals(1, messages.length); - assertEquals("A Reply", messages[0]); + assertEquals("A Reply", messages[0].getText()); assertFalse(newSbn.getNotification().extras .getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false)); assertTrue(newSbn.getNotification().extras @@ -166,11 +185,12 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { @Test public void testRebuildWithRemoteInput_noExistingInputWithSpinner() { StatusBarNotification newSbn = - mRemoteInputManager.rebuildNotificationWithRemoteInput(mEntry, "A Reply", true); - CharSequence[] messages = newSbn.getNotification().extras - .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY); + mRemoteInputManager.rebuildNotificationWithRemoteInput( + mEntry, "A Reply", true, null, null); + RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification() + .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); assertEquals(1, messages.length); - assertEquals("A Reply", messages[0]); + assertEquals("A Reply", messages[0].getText()); assertTrue(newSbn.getNotification().extras .getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false)); assertTrue(newSbn.getNotification().extras @@ -181,18 +201,45 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { public void testRebuildWithRemoteInput_withExistingInput() { // Setup a notification entry with 1 remote input. StatusBarNotification newSbn = - mRemoteInputManager.rebuildNotificationWithRemoteInput(mEntry, "A Reply", false); + mRemoteInputManager.rebuildNotificationWithRemoteInput( + mEntry, "A Reply", false, null, null); + NotificationEntry entry = new NotificationEntryBuilder() + .setSbn(newSbn) + .build(); + + // Try rebuilding to add another reply. + newSbn = mRemoteInputManager.rebuildNotificationWithRemoteInput( + entry, "Reply 2", true, null, null); + RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification() + .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); + assertEquals(2, messages.length); + assertEquals("Reply 2", messages[0].getText()); + assertEquals("A Reply", messages[1].getText()); + } + + @Test + public void testRebuildWithRemoteInput_withExistingInput_image() { + // Setup a notification entry with 1 remote input. + Uri uri = mock(Uri.class); + String mimeType = "image/jpeg"; + String text = "image inserted"; + StatusBarNotification newSbn = + mRemoteInputManager.rebuildNotificationWithRemoteInput( + mEntry, text, false, mimeType, uri); NotificationEntry entry = new NotificationEntryBuilder() .setSbn(newSbn) .build(); // Try rebuilding to add another reply. - newSbn = mRemoteInputManager.rebuildNotificationWithRemoteInput(entry, "Reply 2", true); - CharSequence[] messages = newSbn.getNotification().extras - .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY); + newSbn = mRemoteInputManager.rebuildNotificationWithRemoteInput( + entry, "Reply 2", true, null, null); + RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification() + .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); assertEquals(2, messages.length); - assertEquals("Reply 2", messages[0]); - assertEquals("A Reply", messages[1]); + assertEquals("Reply 2", messages[0].getText()); + assertEquals(text, messages[1].getText()); + assertEquals(mimeType, messages[1].getMimeType()); + assertEquals(uri, messages[1].getUri()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java index 5310dd8a61f9..16f105d5a08c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java @@ -52,6 +52,7 @@ public class RankingBuilder { private ArrayList<CharSequence> mSmartReplies = new ArrayList<>(); private boolean mCanBubble = false; private boolean mIsVisuallyInterruptive = false; + private boolean mIsConversation = false; public RankingBuilder() { } @@ -77,6 +78,7 @@ public class RankingBuilder { mSmartReplies = copyList(ranking.getSmartReplies()); mCanBubble = ranking.canBubble(); mIsVisuallyInterruptive = ranking.visuallyInterruptive(); + mIsConversation = ranking.isConversation(); } public Ranking build() { @@ -101,7 +103,8 @@ public class RankingBuilder { mSmartActions, mSmartReplies, mCanBubble, - mIsVisuallyInterruptive); + mIsVisuallyInterruptive, + mIsConversation); return ranking; } @@ -181,6 +184,11 @@ public class RankingBuilder { return this; } + public RankingBuilder setIsConversation(boolean isConversation) { + mIsConversation = isConversation; + return this; + } + public RankingBuilder setImportance(@Importance int importance) { mImportance = importance; return this; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java index 4b2ce0157b23..296d0cef715c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java @@ -45,7 +45,6 @@ import android.app.PendingIntent; import android.content.Intent; import android.graphics.drawable.Icon; import android.os.Handler; -import android.os.Looper; import android.os.UserHandle; import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.NotificationListenerService.RankingMap; @@ -88,7 +87,6 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.NotifRemoteViewCache; import com.android.systemui.statusbar.notification.row.NotificationContentInflater; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder; import com.android.systemui.statusbar.notification.row.RowInflaterTask; import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; @@ -99,6 +97,7 @@ import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.util.Assert; import com.android.systemui.util.leak.LeakDetector; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -162,7 +161,8 @@ public class NotificationEntryManagerTest extends SysuiTestCase { 0, IMPORTANCE_DEFAULT, null, null, - null, null, null, true, sentiment, false, -1, false, null, null, false, false); + null, null, null, true, sentiment, false, -1, false, null, null, false, false, + false); return true; }).when(mRankingMap).getRanking(eq(key), any(Ranking.class)); } @@ -181,7 +181,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { null, null, null, null, null, true, Ranking.USER_SENTIMENT_NEUTRAL, false, -1, - false, smartActions, null, false, false); + false, smartActions, null, false, false, false); return true; }).when(mRankingMap).getRanking(eq(key), any(Ranking.class)); } @@ -189,15 +189,14 @@ public class NotificationEntryManagerTest extends SysuiTestCase { @Before public void setUp() { MockitoAnnotations.initMocks(this); - if (!mDependency.hasInstantiatedDependency(SmartReplyController.class)) { - mDependency.injectMockDependency(SmartReplyController.class); - } + mDependency.injectMockDependency(SmartReplyController.class); mDependency.injectMockDependency(NotificationMediaManager.class); mCountDownLatch = new CountDownLatch(1); + Assert.sMainLooper = TestableLooper.get(this).getLooper(); mDependency.injectTestDependency(Dependency.MAIN_HANDLER, - Handler.createAsync(Looper.myLooper())); + Handler.createAsync(TestableLooper.get(this).getLooper())); when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController); when(mListContainer.getViewParentForNotification(any())).thenReturn( new FrameLayout(mContext)); @@ -207,9 +206,10 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mEntry.expandedIcon = mock(StatusBarIconView.class); - NotificationRowContentBinder contentBinder = new NotificationContentInflater( + NotificationContentInflater contentBinder = new NotificationContentInflater( mock(NotifRemoteViewCache.class), mRemoteInputManager); + contentBinder.setInflateSynchronously(true); when(mNotificationRowComponentBuilder.activatableNotificationView(any())) .thenReturn(mNotificationRowComponentBuilder); @@ -262,6 +262,12 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mEntry.getKey(), Ranking.USER_SENTIMENT_NEUTRAL); } + @After + public void tearDown() { + // CLEAN UP inflation tasks so they don't callback in a future test + mEntry.abortTask(); + } + @Test public void testAddNotification() throws Exception { TestableLooper.get(this).processAllMessages(); @@ -347,9 +353,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase { @Test public void testRemoveNotification() { - // Row inflation happens off thread, so pretend that this test looper is main - Assert.sMainLooper = TestableLooper.get(this).getLooper(); - mEntry.setRow(mRow); mEntryManager.addActiveNotificationForTest(mEntry); @@ -467,9 +470,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase { @Test public void testLifetimeExtenders_whenRetentionEndsNotificationIsRemoved() { - // Row inflation happens off thread, so pretend that this test looper is main - Assert.sMainLooper = TestableLooper.get(this).getLooper(); - // GIVEN an entry manager with a notification whose life has been extended mEntryManager.addActiveNotificationForTest(mEntry); final FakeNotificationLifetimeExtender extender = new FakeNotificationLifetimeExtender(); @@ -560,9 +560,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase { @Test public void testRemoveInterceptor_notInterceptedGetsRemoved() { - // Row inflation happens off thread, so pretend that this test looper is main - Assert.sMainLooper = TestableLooper.get(this).getLooper(); - // GIVEN an entry manager with a notification mEntryManager.addActiveNotificationForTest(mEntry); @@ -625,7 +622,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase { @Test public void testGetNotificationsForCurrentUser_shouldFilterNonCurrentUserNotifications() { - Assert.sMainLooper = TestableLooper.get(this).getLooper(); Notification.Builder n = new Notification.Builder(mContext, "di") .setSmallIcon(R.drawable.ic_person) .setContentTitle("Title") diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java index 7c3665bfe6fb..e4865b633083 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java @@ -60,7 +60,7 @@ public class HighPriorityProviderTest extends SysuiTestCase { final NotificationEntry entry = new NotificationEntryBuilder() .setImportance(IMPORTANCE_HIGH) .build(); - when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getChannel())) + when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getRanking())) .thenReturn(false); // THEN it has high priority @@ -76,7 +76,7 @@ public class HighPriorityProviderTest extends SysuiTestCase { .setNotification(notification) .setImportance(IMPORTANCE_LOW) .build(); - when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getChannel())) + when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getRanking())) .thenReturn(true); // THEN it has high priority @@ -92,7 +92,7 @@ public class HighPriorityProviderTest extends SysuiTestCase { final NotificationEntry entry = new NotificationEntryBuilder() .setNotification(notification) .build(); - when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getChannel())) + when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getRanking())) .thenReturn(false); // THEN it has high priority @@ -109,7 +109,7 @@ public class HighPriorityProviderTest extends SysuiTestCase { .setNotification(notification) .setImportance(IMPORTANCE_LOW) .build(); - when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getChannel())) + when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getRanking())) .thenReturn(false); // THEN it has high priority @@ -126,7 +126,7 @@ public class HighPriorityProviderTest extends SysuiTestCase { .setNotification(notification) .setImportance(IMPORTANCE_MIN) .build(); - when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getChannel())) + when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getRanking())) .thenReturn(false); // THEN it does NOT have high priority @@ -149,7 +149,7 @@ public class HighPriorityProviderTest extends SysuiTestCase { .setNotification(notification) .setChannel(channel) .build(); - when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getChannel())) + when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getRanking())) .thenReturn(true); // THEN it does NOT have high priority diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java index 13f3a5fbfff7..cc3c3ccdc316 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java @@ -42,6 +42,7 @@ import android.net.wifi.WifiManager; import android.os.Handler; import android.provider.Settings; import android.provider.Settings.Global; +import android.telephony.CdmaEriInformation; import android.telephony.NetworkRegistrationInfo; import android.telephony.PhoneStateListener; import android.telephony.ServiceState; @@ -111,6 +112,8 @@ public class NetworkControllerBaseTest extends SysuiTestCase { private NetworkCapabilities mNetCapabilities; private ConnectivityManager.NetworkCallback mNetworkCallback; + private CdmaEriInformation mEriInformation; + @Rule public TestWatcher failWatcher = new TestWatcher() { @Override @@ -155,6 +158,10 @@ public class NetworkControllerBaseTest extends SysuiTestCase { mSignalStrength = mock(SignalStrength.class); mServiceState = mock(ServiceState.class); + mEriInformation = new CdmaEriInformation(CdmaEriInformation.ERI_OFF, + CdmaEriInformation.ERI_ICON_MODE_NORMAL); + when(mMockTm.getCdmaEriInformation()).thenReturn(mEriInformation); + mConfig = new Config(); mConfig.hspaDataDistinguishable = true; mCallbackHandler = mock(CallbackHandler.class); @@ -305,11 +312,9 @@ public class NetworkControllerBaseTest extends SysuiTestCase { } public void setCdmaRoaming(boolean isRoaming) { - when(mServiceState.getCdmaEriIconIndex()).thenReturn(isRoaming ? - EriInfo.ROAMING_INDICATOR_ON : EriInfo.ROAMING_INDICATOR_OFF); - when(mServiceState.getCdmaEriIconMode()).thenReturn(isRoaming ? - EriInfo.ROAMING_ICON_MODE_NORMAL : -1); - updateServiceState(); + mEriInformation.setEriIconIndex(isRoaming ? + CdmaEriInformation.ERI_ON : CdmaEriInformation.ERI_OFF); + when(mMockTm.getCdmaEriInformation()).thenReturn(mEriInformation); } public void setVoiceRegState(int voiceRegState) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java index cd89d3c32697..4406248ec9ea 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java @@ -36,7 +36,6 @@ import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; -import com.android.internal.telephony.TelephonyIntents; import com.android.settingslib.graph.SignalDrawable; import com.android.settingslib.net.DataUsageController; import com.android.systemui.R; @@ -411,13 +410,13 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { boolean showPlmn, String plmn) { Intent intent = new Intent(); - intent.setAction(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION); + intent.setAction(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED); - intent.putExtra(TelephonyIntents.EXTRA_SHOW_SPN, showSpn); - intent.putExtra(TelephonyIntents.EXTRA_SPN, spn); + intent.putExtra(TelephonyManager.EXTRA_SHOW_SPN, showSpn); + intent.putExtra(TelephonyManager.EXTRA_SPN, spn); - intent.putExtra(TelephonyIntents.EXTRA_SHOW_PLMN, showPlmn); - intent.putExtra(TelephonyIntents.EXTRA_PLMN, plmn); + intent.putExtra(TelephonyManager.EXTRA_SHOW_PLMN, showPlmn); + intent.putExtra(TelephonyManager.EXTRA_PLMN, plmn); SubscriptionManager.putSubscriptionIdExtra(intent, mSubId); return intent; diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt index a785d4ba2cb8..3f3f4d1681a0 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 @@ -23,6 +23,7 @@ import org.junit.Assert.assertFalse import org.junit.Assert.assertNotEquals import org.junit.Assert.assertTrue import org.junit.Before +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyFloat @@ -446,6 +447,7 @@ class PhysicsAnimatorTest : SysuiTestCase() { } @Test + @Ignore("Sporadically flaking.") fun testFlingThenSpring() { PhysicsAnimatorTestUtils.setAllAnimationsBlock(false) diff --git a/packages/Tethering/common/TetheringLib/Android.bp b/packages/Tethering/common/TetheringLib/Android.bp index 264ce440f59f..a8d1239a3c30 100644 --- a/packages/Tethering/common/TetheringLib/Android.bp +++ b/packages/Tethering/common/TetheringLib/Android.bp @@ -36,6 +36,7 @@ java_library { sdk_version: "system_current", srcs: [ "src/android/net/TetheringManager.java", + "src/android/net/TetheringConstants.java", ":framework-tethering-annotations", ], static_libs: [ @@ -63,9 +64,11 @@ filegroup { name: "framework-tethering-srcs", srcs: [ "src/android/net/TetheringManager.java", + "src/android/net/TetheringConstants.java", "src/android/net/IIntResultListener.aidl", "src/android/net/ITetheringEventCallback.aidl", "src/android/net/ITetheringConnector.aidl", + "src/android/net/TetheringCallbackStartedParcel.aidl", "src/android/net/TetheringConfigurationParcel.aidl", "src/android/net/TetherStatesParcel.aidl", ], diff --git a/packages/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl b/packages/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl index 28361954e11e..28a810dbfac3 100644 --- a/packages/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl +++ b/packages/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl @@ -18,6 +18,7 @@ package android.net; import android.net.Network; import android.net.TetheringConfigurationParcel; +import android.net.TetheringCallbackStartedParcel; import android.net.TetherStatesParcel; /** @@ -26,8 +27,8 @@ import android.net.TetherStatesParcel; */ oneway interface ITetheringEventCallback { - void onCallbackStarted(in Network network, in TetheringConfigurationParcel config, - in TetherStatesParcel states); + /** Called immediately after the callbacks are registered */ + void onCallbackStarted(in TetheringCallbackStartedParcel parcel); void onCallbackStopped(int errorCode); void onUpstreamChanged(in Network network); void onConfigurationChanged(in TetheringConfigurationParcel config); diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl b/packages/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl new file mode 100644 index 000000000000..14ee2d3e5d38 --- /dev/null +++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.net.Network; +import android.net.TetheringConfigurationParcel; +import android.net.TetherStatesParcel; + +/** + * Initial information reported by tethering upon callback registration. + * @hide + */ +parcelable TetheringCallbackStartedParcel { + boolean tetheringSupported; + Network upstreamNetwork; + TetheringConfigurationParcel config; + TetherStatesParcel states; +}
\ No newline at end of file diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java new file mode 100644 index 000000000000..00cf98e0fc2d --- /dev/null +++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java @@ -0,0 +1,66 @@ +/* + * 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.os.ResultReceiver; + +/** + * Collections of constants for internal tethering usage. + * + * <p>These hidden constants are not in TetheringManager as they are not part of the API stubs + * generated for TetheringManager, which prevents the tethering module from linking them at + * build time. + * TODO: investigate changing the tethering build rules so that Tethering can reference hidden + * symbols from framework-tethering even when they are in a non-hidden class. + * @hide + */ +public class TetheringConstants { + /** + * Extra used for communicating with the TetherService. Includes the type of tethering to + * enable if any. + * + * {@hide} + */ + public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType"; + /** + * Extra used for communicating with the TetherService. Includes the type of tethering for + * which to cancel provisioning. + * + * {@hide} + */ + public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType"; + /** + * Extra used for communicating with the TetherService. True to schedule a recheck of tether + * provisioning. + * + * {@hide} + */ + public static final String EXTRA_SET_ALARM = "extraSetAlarm"; + /** + * Tells the TetherService to run a provision check now. + * + * {@hide} + */ + public static final String EXTRA_RUN_PROVISION = "extraRunProvision"; + /** + * Extra used for communicating with the TetherService. Contains the {@link ResultReceiver} + * which will receive provisioning results. Can be left empty. + * + * {@hide} + */ + public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback"; +} diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java index 11e57186c666..e1b9c16b8185 100644 --- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java +++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java @@ -16,8 +16,12 @@ package android.net; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.annotation.TestApi; import android.content.Context; -import android.net.ConnectivityManager.OnTetheringEventCallback; +import android.os.Bundle; import android.os.ConditionVariable; import android.os.IBinder; import android.os.RemoteException; @@ -25,6 +29,11 @@ import android.os.ResultReceiver; import android.util.ArrayMap; import android.util.Log; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Objects; import java.util.concurrent.Executor; /** @@ -34,7 +43,8 @@ import java.util.concurrent.Executor; * * @hide */ -// TODO: make it @SystemApi +@SystemApi +@TestApi public class TetheringManager { private static final String TAG = TetheringManager.class.getSimpleName(); private static final int DEFAULT_TIMEOUT_MS = 60_000; @@ -44,7 +54,7 @@ public class TetheringManager { private final ITetheringConnector mConnector; private final TetheringCallbackInternal mCallback; private final Context mContext; - private final ArrayMap<OnTetheringEventCallback, ITetheringEventCallback> + private final ArrayMap<TetheringEventCallback, ITetheringEventCallback> mTetheringEventCallbacks = new ArrayMap<>(); private TetheringConfigurationParcel mTetheringConfiguration; @@ -72,7 +82,7 @@ public class TetheringManager { * gives a String[] listing all the interfaces currently in local-only * mode (ie, has DHCPv4+IPv6-ULA support and no packet forwarding) */ - public static final String EXTRA_ACTIVE_LOCAL_ONLY = "localOnlyArray"; + public static final String EXTRA_ACTIVE_LOCAL_ONLY = "android.net.extra.ACTIVE_LOCAL_ONLY"; /** * gives a String[] listing all the interfaces currently tethered @@ -118,35 +128,6 @@ public class TetheringManager { */ public static final int TETHERING_WIFI_P2P = 3; - /** - * Extra used for communicating with the TetherService. Includes the type of tethering to - * enable if any. - */ - public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType"; - - /** - * Extra used for communicating with the TetherService. Includes the type of tethering for - * which to cancel provisioning. - */ - public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType"; - - /** - * Extra used for communicating with the TetherService. True to schedule a recheck of tether - * provisioning. - */ - public static final String EXTRA_SET_ALARM = "extraSetAlarm"; - - /** - * Tells the TetherService to run a provision check now. - */ - public static final String EXTRA_RUN_PROVISION = "extraRunProvision"; - - /** - * Extra used for communicating with the TetherService. Contains the {@link ResultReceiver} - * which will receive provisioning results. Can be left empty. - */ - public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback"; - public static final int TETHER_ERROR_NO_ERROR = 0; public static final int TETHER_ERROR_UNKNOWN_IFACE = 1; public static final int TETHER_ERROR_SERVICE_UNAVAIL = 2; @@ -160,12 +141,14 @@ public class TetheringManager { public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10; public static final int TETHER_ERROR_PROVISION_FAILED = 11; public static final int TETHER_ERROR_DHCPSERVER_ERROR = 12; - public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13; + public static final int TETHER_ERROR_ENTITLEMENT_UNKNOWN = 13; public static final int TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION = 14; public static final int TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION = 15; /** * Create a TetheringManager object for interacting with the tethering service. + * + * {@hide} */ public TetheringManager(@NonNull final Context context, @NonNull final IBinder service) { mContext = context; @@ -229,10 +212,9 @@ public class TetheringManager { private final ConditionVariable mWaitForCallback = new ConditionVariable(); @Override - public void onCallbackStarted(Network network, TetheringConfigurationParcel config, - TetherStatesParcel states) { - mTetheringConfiguration = config; - mTetherStatesParcel = states; + public void onCallbackStarted(TetheringCallbackStartedParcel parcel) { + mTetheringConfiguration = parcel.config; + mTetherStatesParcel = parcel.states; mWaitForCallback.open(); } @@ -275,6 +257,8 @@ public class TetheringManager { * * @param iface the interface name to tether. * @return error a {@code TETHER_ERROR} value indicating success or failure type + * + * {@hide} */ @Deprecated public int tether(@NonNull final String iface) { @@ -296,6 +280,8 @@ public class TetheringManager { * * @deprecated The only usages is PanService. It uses this for legacy reasons * and will migrate away as soon as possible. + * + * {@hide} */ @Deprecated public int untether(@NonNull final String iface) { @@ -320,6 +306,8 @@ public class TetheringManager { * #startTethering or #stopTethering which encapsulate proper entitlement logic. If the API is * used and an entitlement check is needed, downstream USB tethering will be enabled but will * not have any upstream. + * + * {@hide} */ @Deprecated public int setUsbTethering(final boolean enable) { @@ -340,7 +328,7 @@ public class TetheringManager { /** * Starts tethering and runs tether provisioning for the given type if needed. If provisioning * fails, stopTethering will be called automatically. - * + * @hide */ // TODO: improve the usage of ResultReceiver, b/145096122 public void startTethering(final int type, @NonNull final ResultReceiver receiver, @@ -375,11 +363,63 @@ public class TetheringManager { } /** + * Callback for use with {@link #getLatestTetheringEntitlementResult} to find out whether + * entitlement succeeded. + */ + public interface OnTetheringEntitlementResultListener { + /** + * Called to notify entitlement result. + * + * @param resultCode an int value of entitlement result. It may be one of + * {@link #TETHER_ERROR_NO_ERROR}, + * {@link #TETHER_ERROR_PROVISION_FAILED}, or + * {@link #TETHER_ERROR_ENTITLEMENT_UNKNOWN}. + */ + void onTetheringEntitlementResult(int resultCode); + } + + /** * Request the latest value of the tethering entitlement check. * - * Note: Allow privileged apps who have TETHER_PRIVILEGED permission to access. If it turns - * out some such apps are observed to abuse this API, change to per-UID limits on this API - * if it's really needed. + * <p>This method will only return the latest entitlement result if it is available. If no + * cached entitlement result is available, and {@code showEntitlementUi} is false, + * {@link #TETHER_ERROR_ENTITLEMENT_UNKNOWN} will be returned. If {@code showEntitlementUi} is + * true, entitlement will be run. + * + * @param type the downstream type of tethering. Must be one of {@code #TETHERING_*} constants. + * @param showEntitlementUi a boolean indicating whether to run UI-based entitlement check. + * @param executor the executor on which callback will be invoked. + * @param listener an {@link OnTetheringEntitlementResultListener} which will be called to + * notify the caller of the result of entitlement check. The listener may be called zero + * or one time. + */ + @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) + public void requestLatestTetheringEntitlementResult(int type, boolean showEntitlementUi, + @NonNull Executor executor, + @NonNull final OnTetheringEntitlementResultListener listener) { + if (listener == null) { + throw new IllegalArgumentException( + "OnTetheringEntitlementResultListener cannot be null."); + } + + ResultReceiver wrappedListener = new ResultReceiver(null /* handler */) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + executor.execute(() -> { + listener.onTetheringEntitlementResult(resultCode); + }); + } + }; + + requestLatestTetheringEntitlementResult(type, wrappedListener, + showEntitlementUi); + } + + /** + * Helper function of #requestLatestTetheringEntitlementResult to remain backwards compatible + * with ConnectivityManager#getLatestTetheringEntitlementResult + * + * {@hide} */ // TODO: improve the usage of ResultReceiver, b/145096122 public void requestLatestTetheringEntitlementResult(final int type, @@ -396,9 +436,126 @@ public class TetheringManager { } /** + * Callback for use with {@link registerTetheringEventCallback} to find out tethering + * upstream status. + */ + public abstract static class TetheringEventCallback { + /** + * Called when tethering supported status changed. + * + * <p>This will be called immediately after the callback is registered, and may be called + * multiple times later upon changes. + * + * <p>Tethering may be disabled via system properties, device configuration, or device + * policy restrictions. + * + * @param supported The new supported status + */ + public void onTetheringSupported(boolean supported) {} + + /** + * Called when tethering upstream changed. + * + * <p>This will be called immediately after the callback is registered, and may be called + * multiple times later upon changes. + * + * @param network the {@link Network} of tethering upstream. Null means tethering doesn't + * have any upstream. + */ + public void onUpstreamChanged(@Nullable Network network) {} + + /** + * Called when there was a change in tethering interface regular expressions. + * + * <p>This will be called immediately after the callback is registered, and may be called + * multiple times later upon changes. + * @param reg The new regular expressions. + * @deprecated Referencing interfaces by regular expressions is a deprecated mechanism. + */ + @Deprecated + public void onTetherableInterfaceRegexpsChanged(@NonNull TetheringInterfaceRegexps reg) {} + + /** + * Called when there was a change in the list of tetherable interfaces. + * + * <p>This will be called immediately after the callback is registered, and may be called + * multiple times later upon changes. + * @param interfaces The list of tetherable interfaces. + */ + public void onTetherableInterfacesChanged(@NonNull List<String> interfaces) {} + + /** + * Called when there was a change in the list of tethered interfaces. + * + * <p>This will be called immediately after the callback is registered, and may be called + * multiple times later upon changes. + * @param interfaces The list of tethered interfaces. + */ + public void onTetheredInterfacesChanged(@NonNull List<String> interfaces) {} + + /** + * Called when an error occurred configuring tethering. + * + * <p>This will be called immediately after the callback is registered if the latest status + * on the interface is an error, and may be called multiple times later upon changes. + * @param ifName Name of the interface. + * @param error One of {@code TetheringManager#TETHER_ERROR_*}. + */ + public void onError(@NonNull String ifName, int error) {} + } + + /** + * Regular expressions used to identify tethering interfaces. + * @deprecated Referencing interfaces by regular expressions is a deprecated mechanism. + */ + @Deprecated + public static class TetheringInterfaceRegexps { + private final String[] mTetherableBluetoothRegexs; + private final String[] mTetherableUsbRegexs; + private final String[] mTetherableWifiRegexs; + + public TetheringInterfaceRegexps(@NonNull String[] tetherableBluetoothRegexs, + @NonNull String[] tetherableUsbRegexs, @NonNull String[] tetherableWifiRegexs) { + mTetherableBluetoothRegexs = tetherableBluetoothRegexs.clone(); + mTetherableUsbRegexs = tetherableUsbRegexs.clone(); + mTetherableWifiRegexs = tetherableWifiRegexs.clone(); + } + + @NonNull + public List<String> getTetherableBluetoothRegexs() { + return Collections.unmodifiableList(Arrays.asList(mTetherableBluetoothRegexs)); + } + + @NonNull + public List<String> getTetherableUsbRegexs() { + return Collections.unmodifiableList(Arrays.asList(mTetherableUsbRegexs)); + } + + @NonNull + public List<String> getTetherableWifiRegexs() { + return Collections.unmodifiableList(Arrays.asList(mTetherableWifiRegexs)); + } + + @Override + public int hashCode() { + return Objects.hash(mTetherableBluetoothRegexs, mTetherableUsbRegexs, + mTetherableWifiRegexs); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof TetheringInterfaceRegexps)) return false; + final TetheringInterfaceRegexps other = (TetheringInterfaceRegexps) obj; + return Arrays.equals(mTetherableBluetoothRegexs, other.mTetherableBluetoothRegexs) + && Arrays.equals(mTetherableUsbRegexs, other.mTetherableUsbRegexs) + && Arrays.equals(mTetherableWifiRegexs, other.mTetherableWifiRegexs); + } + } + + /** * Start listening to tethering change events. Any new added callback will receive the last * tethering status right away. If callback is registered, - * {@link OnTetheringEventCallback#onUpstreamChanged} will immediately be called. If tethering + * {@link TetheringEventCallback#onUpstreamChanged} will immediately be called. If tethering * has no upstream or disabled, the argument of callback will be null. The same callback object * cannot be registered twice. * @@ -406,15 +563,20 @@ public class TetheringManager { * @param callback the callback to be called when tethering has change events. */ public void registerTetheringEventCallback(@NonNull Executor executor, - @NonNull OnTetheringEventCallback callback) { + @NonNull TetheringEventCallback callback) { final String callerPkg = mContext.getOpPackageName(); Log.i(TAG, "registerTetheringEventCallback caller:" + callerPkg); synchronized (mTetheringEventCallbacks) { - if (!mTetheringEventCallbacks.containsKey(callback)) { + if (mTetheringEventCallbacks.containsKey(callback)) { throw new IllegalArgumentException("callback was already registered."); } final ITetheringEventCallback remoteCallback = new ITetheringEventCallback.Stub() { + // Only accessed with a lock on this object + private final HashMap<String, Integer> mErrorStates = new HashMap<>(); + private String[] mLastTetherableInterfaces = null; + private String[] mLastTetheredInterfaces = null; + @Override public void onUpstreamChanged(Network network) throws RemoteException { executor.execute(() -> { @@ -422,11 +584,45 @@ public class TetheringManager { }); } + private synchronized void sendErrorCallbacks(final TetherStatesParcel newStates) { + for (int i = 0; i < newStates.erroredIfaceList.length; i++) { + final String iface = newStates.erroredIfaceList[i]; + final Integer lastError = mErrorStates.get(iface); + final int newError = newStates.lastErrorList[i]; + if (newError != TETHER_ERROR_NO_ERROR + && !Objects.equals(lastError, newError)) { + callback.onError(iface, newError); + } + mErrorStates.put(iface, newError); + } + } + + private synchronized void maybeSendTetherableIfacesChangedCallback( + final TetherStatesParcel newStates) { + if (Arrays.equals(mLastTetherableInterfaces, newStates.availableList)) return; + mLastTetherableInterfaces = newStates.availableList.clone(); + callback.onTetherableInterfacesChanged( + Collections.unmodifiableList(Arrays.asList(mLastTetherableInterfaces))); + } + + private synchronized void maybeSendTetheredIfacesChangedCallback( + final TetherStatesParcel newStates) { + if (Arrays.equals(mLastTetheredInterfaces, newStates.tetheredList)) return; + mLastTetheredInterfaces = newStates.tetheredList.clone(); + callback.onTetheredInterfacesChanged( + Collections.unmodifiableList(Arrays.asList(mLastTetheredInterfaces))); + } + + // Called immediately after the callbacks are registered. @Override - public void onCallbackStarted(Network network, TetheringConfigurationParcel config, - TetherStatesParcel states) { + public void onCallbackStarted(TetheringCallbackStartedParcel parcel) { executor.execute(() -> { - callback.onUpstreamChanged(network); + callback.onTetheringSupported(parcel.tetheringSupported); + callback.onUpstreamChanged(parcel.upstreamNetwork); + sendErrorCallbacks(parcel.states); + sendRegexpsChanged(parcel.config); + maybeSendTetherableIfacesChangedCallback(parcel.states); + maybeSendTetheredIfacesChangedCallback(parcel.states); }); } @@ -437,11 +633,26 @@ public class TetheringManager { }); } + private void sendRegexpsChanged(TetheringConfigurationParcel parcel) { + callback.onTetherableInterfaceRegexpsChanged(new TetheringInterfaceRegexps( + parcel.tetherableBluetoothRegexs, + parcel.tetherableUsbRegexs, + parcel.tetherableWifiRegexs)); + } + @Override - public void onConfigurationChanged(TetheringConfigurationParcel config) { } + public void onConfigurationChanged(TetheringConfigurationParcel config) { + executor.execute(() -> sendRegexpsChanged(config)); + } @Override - public void onTetherStatesChanged(TetherStatesParcel states) { } + public void onTetherStatesChanged(TetherStatesParcel states) { + executor.execute(() -> { + sendErrorCallbacks(states); + maybeSendTetherableIfacesChangedCallback(states); + maybeSendTetheredIfacesChangedCallback(states); + }); + } }; try { mConnector.registerTetheringEventCallback(remoteCallback, callerPkg); @@ -458,7 +669,7 @@ public class TetheringManager { * * @param callback previously registered callback. */ - public void unregisterTetheringEventCallback(@NonNull final OnTetheringEventCallback callback) { + public void unregisterTetheringEventCallback(@NonNull final TetheringEventCallback callback) { final String callerPkg = mContext.getOpPackageName(); Log.i(TAG, "unregisterTetheringEventCallback caller:" + callerPkg); @@ -482,6 +693,7 @@ public class TetheringManager { * @param iface The name of the interface of interest * @return error The error code of the last error tethering or untethering the named * interface + * @hide */ public int getLastTetherError(@NonNull final String iface) { mCallback.waitForStarted(); @@ -503,6 +715,7 @@ public class TetheringManager { * * @return an array of 0 or more regular expression Strings defining * what interfaces are considered tetherable usb interfaces. + * @hide */ public @NonNull String[] getTetherableUsbRegexs() { mCallback.waitForStarted(); @@ -516,6 +729,7 @@ public class TetheringManager { * * @return an array of 0 or more regular expression Strings defining * what interfaces are considered tetherable wifi interfaces. + * @hide */ public @NonNull String[] getTetherableWifiRegexs() { mCallback.waitForStarted(); @@ -529,6 +743,7 @@ public class TetheringManager { * * @return an array of 0 or more regular expression Strings defining * what interfaces are considered tetherable bluetooth interfaces. + * @hide */ public @NonNull String[] getTetherableBluetoothRegexs() { mCallback.waitForStarted(); @@ -540,6 +755,7 @@ public class TetheringManager { * device configuration and current interface existence. * * @return an array of 0 or more Strings of tetherable interface names. + * @hide */ public @NonNull String[] getTetherableIfaces() { mCallback.waitForStarted(); @@ -552,6 +768,7 @@ public class TetheringManager { * Get the set of tethered interfaces. * * @return an array of 0 or more String of currently tethered interface names. + * @hide */ public @NonNull String[] getTetheredIfaces() { mCallback.waitForStarted(); @@ -570,6 +787,7 @@ public class TetheringManager { * * @return an array of 0 or more String indicating the interface names * which failed to tether. + * @hide */ public @NonNull String[] getTetheringErroredIfaces() { mCallback.waitForStarted(); @@ -582,6 +800,7 @@ public class TetheringManager { * Get the set of tethered dhcp ranges. * * @deprecated This API just return the default value which is not used in DhcpServer. + * @hide */ @Deprecated public @NonNull String[] getTetheredDhcpRanges() { @@ -595,6 +814,7 @@ public class TetheringManager { * due to device configuration. * * @return a boolean - {@code true} indicating Tethering is supported. + * @hide */ public boolean isTetheringSupported() { final String callerPkg = mContext.getOpPackageName(); diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java b/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java index 1cabc8d0b5b7..e81d6ac7a588 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java @@ -16,14 +16,14 @@ package com.android.server.connectivity.tethering; -import static android.net.TetheringManager.EXTRA_ADD_TETHER_TYPE; -import static android.net.TetheringManager.EXTRA_PROVISION_CALLBACK; -import static android.net.TetheringManager.EXTRA_RUN_PROVISION; +import static android.net.TetheringConstants.EXTRA_ADD_TETHER_TYPE; +import static android.net.TetheringConstants.EXTRA_PROVISION_CALLBACK; +import static android.net.TetheringConstants.EXTRA_RUN_PROVISION; import static android.net.TetheringManager.TETHERING_BLUETOOTH; import static android.net.TetheringManager.TETHERING_INVALID; import static android.net.TetheringManager.TETHERING_USB; import static android.net.TetheringManager.TETHERING_WIFI; -import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKONWN; +import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN; import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; import static android.net.TetheringManager.TETHER_ERROR_PROVISION_FAILED; @@ -577,7 +577,7 @@ public class EntitlementManager { private static String errorString(int value) { switch (value) { - case TETHER_ERROR_ENTITLEMENT_UNKONWN: return "TETHER_ERROR_ENTITLEMENT_UNKONWN"; + case TETHER_ERROR_ENTITLEMENT_UNKNOWN: return "TETHER_ERROR_ENTITLEMENT_UNKONWN"; case TETHER_ERROR_NO_ERROR: return "TETHER_ERROR_NO_ERROR"; case TETHER_ERROR_PROVISION_FAILED: return "TETHER_ERROR_PROVISION_FAILED"; default: @@ -657,7 +657,7 @@ public class EntitlementManager { } final int cacheValue = mEntitlementCacheValue.get( - downstream, TETHER_ERROR_ENTITLEMENT_UNKONWN); + downstream, TETHER_ERROR_ENTITLEMENT_UNKNOWN); if (cacheValue == TETHER_ERROR_NO_ERROR || !showEntitlementUi) { receiver.send(cacheValue, null); } else { diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java index 8d1e0c96e300..5370145f1992 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java @@ -74,6 +74,7 @@ import android.net.LinkProperties; import android.net.Network; import android.net.NetworkInfo; import android.net.TetherStatesParcel; +import android.net.TetheringCallbackStartedParcel; import android.net.TetheringConfigurationParcel; import android.net.ip.IpServer; import android.net.shared.NetdUtils; @@ -951,6 +952,7 @@ public class Tethering { mWrapper.showTetheredNotification( R.drawable.stat_sys_tether_general, false); mWrapper.untetherAll(); + // TODO(b/148139325): send tetheringSupported on restriction change } } } @@ -1844,9 +1846,13 @@ public class Tethering { void registerTetheringEventCallback(ITetheringEventCallback callback) { mHandler.post(() -> { mTetheringEventCallbacks.register(callback); + final TetheringCallbackStartedParcel parcel = new TetheringCallbackStartedParcel(); + parcel.tetheringSupported = mDeps.isTetheringSupported(); + parcel.upstreamNetwork = mTetherUpstream; + parcel.config = mConfig.toStableParcelable(); + parcel.states = mTetherStatesParcel; try { - callback.onCallbackStarted(mTetherUpstream, mConfig.toStableParcelable(), - mTetherStatesParcel); + callback.onCallbackStarted(parcel); } catch (RemoteException e) { // Not really very much to do here. } @@ -1881,6 +1887,7 @@ public class Tethering { for (int i = 0; i < length; i++) { try { mTetheringEventCallbacks.getBroadcastItem(i).onConfigurationChanged(config); + // TODO(b/148139325): send tetheringSupported on configuration change } catch (RemoteException e) { // Not really very much to do here. } diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java index 4f0746199786..3a1d4a61a39e 100644 --- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java @@ -19,7 +19,7 @@ package com.android.server.connectivity.tethering; import static android.net.TetheringManager.TETHERING_BLUETOOTH; import static android.net.TetheringManager.TETHERING_USB; import static android.net.TetheringManager.TETHERING_WIFI; -import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKONWN; +import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN; import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; import static android.net.TetheringManager.TETHER_ERROR_PROVISION_FAILED; import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; @@ -110,7 +110,7 @@ public final class EntitlementManagerTest { } public class WrappedEntitlementManager extends EntitlementManager { - public int fakeEntitlementResult = TETHER_ERROR_ENTITLEMENT_UNKONWN; + public int fakeEntitlementResult = TETHER_ERROR_ENTITLEMENT_UNKNOWN; public int uiProvisionCount = 0; public int silentProvisionCount = 0; @@ -120,7 +120,7 @@ public final class EntitlementManagerTest { } public void reset() { - fakeEntitlementResult = TETHER_ERROR_ENTITLEMENT_UNKONWN; + fakeEntitlementResult = TETHER_ERROR_ENTITLEMENT_UNKNOWN; uiProvisionCount = 0; silentProvisionCount = 0; } @@ -274,7 +274,7 @@ public final class EntitlementManagerTest { receiver = new ResultReceiver(null) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { - assertEquals(TETHER_ERROR_ENTITLEMENT_UNKONWN, resultCode); + assertEquals(TETHER_ERROR_ENTITLEMENT_UNKNOWN, resultCode); mCallbacklatch.countDown(); } }; @@ -343,7 +343,7 @@ public final class EntitlementManagerTest { receiver = new ResultReceiver(null) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { - assertEquals(TETHER_ERROR_ENTITLEMENT_UNKONWN, resultCode); + assertEquals(TETHER_ERROR_ENTITLEMENT_UNKNOWN, resultCode); mCallbacklatch.countDown(); } }; diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java index d6afa4744d26..9f0d8769b1f9 100644 --- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java @@ -86,6 +86,7 @@ import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.RouteInfo; import android.net.TetherStatesParcel; +import android.net.TetheringCallbackStartedParcel; import android.net.TetheringConfigurationParcel; import android.net.dhcp.DhcpServerCallbacks; import android.net.dhcp.DhcpServingParamsParcel; @@ -1113,11 +1114,10 @@ public class TetheringTest { } @Override - public void onCallbackStarted(Network network, TetheringConfigurationParcel config, - TetherStatesParcel states) { - mActualUpstreams.add(network); - mTetheringConfigs.add(config); - mTetherStates.add(states); + public void onCallbackStarted(TetheringCallbackStartedParcel parcel) { + mActualUpstreams.add(parcel.upstreamNetwork); + mTetheringConfigs.add(parcel.config); + mTetherStates.add(parcel.states); } @Override diff --git a/packages/services/PacProcessor/jni/Android.bp b/packages/services/PacProcessor/jni/Android.bp index 61f8143e68b5..ab21a76229ba 100644 --- a/packages/services/PacProcessor/jni/Android.bp +++ b/packages/services/PacProcessor/jni/Android.bp @@ -39,8 +39,5 @@ cc_library_shared { ], sanitize: { cfi: true, - diag: { - cfi: true, - }, }, } diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java index 5e10916c4491..0bcf45d4a526 100644 --- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java +++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java @@ -47,6 +47,7 @@ import android.os.RemoteException; import android.os.SELinux; import android.os.UserHandle; import android.os.WorkSource; +import android.util.FeatureFlagUtils; import android.util.Log; import com.android.internal.annotations.GuardedBy; @@ -399,6 +400,12 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { * the transport have no data. */ private void informTransportOfUnchangedApps(Set<String> appsBackedUp) { + // If the feautre is not enabled then we just exit early. + if (!FeatureFlagUtils.isEnabled(mBackupManagerService.getContext(), + FeatureFlagUtils.BACKUP_NO_KV_DATA_CHANGE_CALLS)) { + return; + } + String[] succeedingPackages = getSucceedingPackages(); if (succeedingPackages == null) { // Nothing is succeeding, so end early. diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java index 9cdb58d8c019..b54ec4ea9441 100644 --- a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java +++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java @@ -27,6 +27,7 @@ import android.app.contentsuggestions.IContentSuggestionsManager; import android.app.contentsuggestions.ISelectionsCallback; import android.app.contentsuggestions.SelectionsRequest; import android.content.Context; +import android.graphics.Bitmap; import android.os.Binder; import android.os.Bundle; import android.os.RemoteException; @@ -61,6 +62,10 @@ public class ContentSuggestionsManagerService extends private static final boolean VERBOSE = false; // TODO: make dynamic private static final int MAX_TEMP_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes + /** + * Key into the extras Bundle passed to {@link #provideContextImage(int, Bundle)}. + */ + private static final String EXTRA_BITMAP = "android.contentsuggestions.extra.BITMAP"; private ActivityTaskManagerInternal mActivityTaskManagerInternal; @@ -111,6 +116,33 @@ public class ContentSuggestionsManagerService extends private class ContentSuggestionsManagerStub extends IContentSuggestionsManager.Stub { @Override + public void provideContextBitmap( + int userId, + @NonNull Bitmap bitmap, + @NonNull Bundle imageContextRequestExtras) { + if (bitmap == null) { + throw new IllegalArgumentException("Expected non-null bitmap"); + } + if (imageContextRequestExtras == null) { + throw new IllegalArgumentException("Expected non-null imageContextRequestExtras"); + } + enforceCaller(UserHandle.getCallingUserId(), "provideContextBitmap"); + + synchronized (mLock) { + final ContentSuggestionsPerUserService service = getServiceForUserLocked(userId); + if (service != null) { + // TODO(b/147324195): Temporarily pass bitmap until we change the service API. + imageContextRequestExtras.putParcelable(EXTRA_BITMAP, bitmap); + service.provideContextImageLocked(/* taskId = */ -1, imageContextRequestExtras); + } else { + if (VERBOSE) { + Slog.v(TAG, "provideContextImageLocked: no service for " + userId); + } + } + } + } + + @Override public void provideContextImage( int userId, int taskId, diff --git a/services/core/Android.bp b/services/core/Android.bp index 02d4f94662b5..1691a96b0dc4 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -83,6 +83,7 @@ java_library_static { ":storaged_aidl", ":vold_aidl", ":platform-compat-config", + ":display-device-config", "java/com/android/server/EventLogTags.logtags", "java/com/android/server/am/EventLogTags.logtags", "java/com/android/server/wm/EventLogTags.logtags", diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 368416b0d4a1..c3cc5dee0e14 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -33,6 +33,7 @@ import android.content.pm.parsing.AndroidPackage; import android.content.pm.parsing.ComponentParseUtils; import android.os.Bundle; import android.os.PersistableBundle; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.SparseArray; @@ -689,6 +690,27 @@ public abstract class PackageManagerInternal { int userId); /** + * Return the processes that have been declared for a uid. + * + * @param uid The uid to query. + * + * @return Returns null if there are no declared processes for the uid; otherwise, + * returns the set of processes it declared. + */ + public abstract ArrayMap<String, ProcessInfo> getProcessesForUid(int uid); + + /** + * Return the gids associated with a particular permission. + * + * @param permissionName The name of the permission to query. + * @param userId The user id the gids will be associated with. + * + * @return Returns null if there are no gids associated with the permission, otherwise an + * array if the gid ints. + */ + public abstract int[] getPermissionGids(String permissionName, int userId); + + /** * Return if device is currently in a "core" boot environment, typically * used to support full-disk encryption. Only apps marked with * {@code coreApp} attribute are available. diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 7ab345387fab..1c9f5dc9c2f1 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -1571,48 +1571,49 @@ public class ConnectivityService extends IConnectivityManager.Stub enforceAccessPermission(); final int uid = Binder.getCallingUid(); NetworkState state = getUnfilteredActiveNetworkState(uid); - return state.linkProperties; + if (state.linkProperties == null) return null; + return linkPropertiesRestrictedForCallerPermissions(state.linkProperties, + Binder.getCallingPid(), uid); } @Override public LinkProperties getLinkPropertiesForType(int networkType) { enforceAccessPermission(); NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType); - if (nai != null) { - synchronized (nai) { - return new LinkProperties(nai.linkProperties); - } - } - return null; + final LinkProperties lp = getLinkProperties(nai); + if (lp == null) return null; + return linkPropertiesRestrictedForCallerPermissions( + lp, Binder.getCallingPid(), Binder.getCallingUid()); } // TODO - this should be ALL networks @Override public LinkProperties getLinkProperties(Network network) { enforceAccessPermission(); - return getLinkProperties(getNetworkAgentInfoForNetwork(network)); + final LinkProperties lp = getLinkProperties(getNetworkAgentInfoForNetwork(network)); + if (lp == null) return null; + return linkPropertiesRestrictedForCallerPermissions( + lp, Binder.getCallingPid(), Binder.getCallingUid()); } - private LinkProperties getLinkProperties(NetworkAgentInfo nai) { + @Nullable + private LinkProperties getLinkProperties(@Nullable NetworkAgentInfo nai) { if (nai == null) { return null; } synchronized (nai) { - return new LinkProperties(nai.linkProperties); + return nai.linkProperties; } } private NetworkCapabilities getNetworkCapabilitiesInternal(NetworkAgentInfo nai) { - if (nai != null) { - synchronized (nai) { - if (nai.networkCapabilities != null) { - return networkCapabilitiesRestrictedForCallerPermissions( - nai.networkCapabilities, - Binder.getCallingPid(), Binder.getCallingUid()); - } - } + if (nai == null) return null; + synchronized (nai) { + if (nai.networkCapabilities == null) return null; + return networkCapabilitiesRestrictedForCallerPermissions( + nai.networkCapabilities, + Binder.getCallingPid(), Binder.getCallingUid()); } - return null; } @Override @@ -1634,6 +1635,29 @@ public class ConnectivityService extends IConnectivityManager.Stub return newNc; } + private LinkProperties linkPropertiesRestrictedForCallerPermissions( + LinkProperties lp, int callerPid, int callerUid) { + if (lp == null) return new LinkProperties(); + + // Only do a permission check if sanitization is needed, to avoid unnecessary binder calls. + final boolean needsSanitization = + (lp.getCaptivePortalApiUrl() != null || lp.getCaptivePortalData() != null); + if (!needsSanitization) { + return new LinkProperties(lp); + } + + if (checkSettingsPermission(callerPid, callerUid)) { + return lp.makeSensitiveFieldsParcelingCopy(); + } + + final LinkProperties newLp = new LinkProperties(lp); + // Sensitive fields would not be parceled anyway, but sanitize for consistency before the + // object gets parceled. + newLp.setCaptivePortalApiUrl(null); + newLp.setCaptivePortalData(null); + return newLp; + } + private void restrictRequestUidsForCaller(NetworkCapabilities nc) { if (!checkSettingsPermission()) { nc.setSingleUid(Binder.getCallingUid()); @@ -6145,7 +6169,8 @@ public class ConnectivityService extends IConnectivityManager.Stub case ConnectivityManager.CALLBACK_AVAILABLE: { putParcelable(bundle, networkCapabilitiesRestrictedForCallerPermissions( networkAgent.networkCapabilities, nri.mPid, nri.mUid)); - putParcelable(bundle, new LinkProperties(networkAgent.linkProperties)); + putParcelable(bundle, linkPropertiesRestrictedForCallerPermissions( + networkAgent.linkProperties, nri.mPid, nri.mUid)); // For this notification, arg1 contains the blocked status. msg.arg1 = arg1; break; @@ -6162,7 +6187,8 @@ public class ConnectivityService extends IConnectivityManager.Stub break; } case ConnectivityManager.CALLBACK_IP_CHANGED: { - putParcelable(bundle, new LinkProperties(networkAgent.linkProperties)); + putParcelable(bundle, linkPropertiesRestrictedForCallerPermissions( + networkAgent.linkProperties, nri.mPid, nri.mUid)); break; } case ConnectivityManager.CALLBACK_BLK_CHANGED: { diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index e9db9c819ab7..003525c91c32 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -18,6 +18,8 @@ package com.android.server; import static android.Manifest.permission.ACCESS_COARSE_LOCATION; import static android.Manifest.permission.ACCESS_FINE_LOCATION; +import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; +import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.location.LocationManager.FUSED_PROVIDER; import static android.location.LocationManager.GPS_PROVIDER; @@ -35,14 +37,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.ResolveInfo; -import android.content.pm.Signature; -import android.content.res.Resources; -import android.hardware.location.ActivityRecognitionHardware; import android.location.Address; import android.location.Criteria; import android.location.GeocoderParams; @@ -52,6 +47,7 @@ import android.location.IBatchedLocationCallback; import android.location.IGnssMeasurementsListener; import android.location.IGnssNavigationMessageListener; import android.location.IGnssStatusListener; +import android.location.IGpsGeofenceHardware; import android.location.ILocationListener; import android.location.ILocationManager; import android.location.Location; @@ -91,11 +87,11 @@ import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.server.location.AbstractLocationProvider; import com.android.server.location.AbstractLocationProvider.State; -import com.android.server.location.ActivityRecognitionProxy; import com.android.server.location.CallerIdentity; import com.android.server.location.GeocoderProxy; import com.android.server.location.GeofenceManager; import com.android.server.location.GeofenceProxy; +import com.android.server.location.HardwareActivityRecognitionProxy; import com.android.server.location.LocationFudger; import com.android.server.location.LocationProviderProxy; import com.android.server.location.LocationRequestStatistics; @@ -114,7 +110,6 @@ import java.io.FileDescriptor; import java.io.PrintStream; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -543,77 +538,6 @@ public class LocationManagerService extends ILocationManager.Stub { } @GuardedBy("mLock") - private void ensureFallbackFusedProviderPresentLocked(String[] pkgs) { - PackageManager pm = mContext.getPackageManager(); - String systemPackageName = mContext.getPackageName(); - ArrayList<HashSet<Signature>> sigSets = ServiceWatcher.getSignatureSets(mContext, pkgs); - - List<ResolveInfo> rInfos = pm.queryIntentServicesAsUser( - new Intent(FUSED_LOCATION_SERVICE_ACTION), - PackageManager.GET_META_DATA, mUserInfoStore.getCurrentUserId()); - for (ResolveInfo rInfo : rInfos) { - String packageName = rInfo.serviceInfo.packageName; - - // Check that the signature is in the list of supported sigs. If it's not in - // this list the standard provider binding logic won't bind to it. - try { - PackageInfo pInfo; - pInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES); - if (!ServiceWatcher.isSignatureMatch(pInfo.signatures, sigSets)) { - Log.w(TAG, packageName + " resolves service " + FUSED_LOCATION_SERVICE_ACTION + - ", but has wrong signature, ignoring"); - continue; - } - } catch (NameNotFoundException e) { - Log.e(TAG, "missing package: " + packageName); - continue; - } - - // Get the version info - if (rInfo.serviceInfo.metaData == null) { - Log.w(TAG, "Found fused provider without metadata: " + packageName); - continue; - } - - int version = rInfo.serviceInfo.metaData.getInt( - ServiceWatcher.EXTRA_SERVICE_VERSION, -1); - if (version == 0) { - // This should be the fallback fused location provider. - - // Make sure it's in the system partition. - if ((rInfo.serviceInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { - if (D) Log.d(TAG, "Fallback candidate not in /system: " + packageName); - continue; - } - - // Check that the fallback is signed the same as the OS - // as a proxy for coreApp="true" - if (pm.checkSignatures(systemPackageName, packageName) - != PackageManager.SIGNATURE_MATCH) { - if (D) { - Log.d(TAG, "Fallback candidate not signed the same as system: " - + packageName); - } - continue; - } - - // Found a valid fallback. - if (D) Log.d(TAG, "Found fallback provider: " + packageName); - return; - } else { - if (D) Log.d(TAG, "Fallback candidate not version 0: " + packageName); - } - } - - throw new IllegalStateException("Unable to find a fused location provider that is in the " - + "system partition with version 0 and signed with the platform certificate. " - + "Such a package is needed to provide a default fused location provider in the " - + "event that no other fused location provider has been installed or is currently " - + "available. For example, coreOnly boot mode when decrypting the data " - + "partition. The fallback must also be marked coreApp=\"true\" in the manifest"); - } - - @GuardedBy("mLock") private void initializeProvidersLocked() { if (GnssManagerService.isGnssSupported()) { mGnssManagerService = new GnssManagerService(this, mContext, mLocationUsageLogger); @@ -622,33 +546,11 @@ public class LocationManagerService extends ILocationManager.Stub { gnssManager.setRealProvider(mGnssManagerService.getGnssLocationProvider()); } - /* - Load package name(s) containing location provider support. - These packages can contain services implementing location providers: - Geocoder Provider, Network Location Provider, and - Fused Location Provider. They will each be searched for - service components implementing these providers. - The location framework also has support for installation - of new location providers at run-time. The new package does not - have to be explicitly listed here, however it must have a signature - that matches the signature of at least one package on this list. - */ - Resources resources = mContext.getResources(); - String[] pkgs = resources.getStringArray( - com.android.internal.R.array.config_locationProviderPackageNames); - if (D) { - Log.d(TAG, "certificates for location providers pulled from: " + - Arrays.toString(pkgs)); - } - - ensureFallbackFusedProviderPresentLocked(pkgs); - - LocationProviderProxy networkProvider = LocationProviderProxy.createAndBind( + LocationProviderProxy networkProvider = LocationProviderProxy.createAndRegister( mContext, NETWORK_LOCATION_SERVICE_ACTION, com.android.internal.R.bool.config_enableNetworkLocationOverlay, - com.android.internal.R.string.config_networkLocationProviderPackageName, - com.android.internal.R.array.config_locationProviderPackageNames); + com.android.internal.R.string.config_networkLocationProviderPackageName); if (networkProvider != null) { LocationProviderManager networkManager = new LocationProviderManager(NETWORK_PROVIDER); mProviderManagers.add(networkManager); @@ -657,13 +559,18 @@ public class LocationManagerService extends ILocationManager.Stub { Slog.w(TAG, "no network location provider found"); } + // ensure that a fused provider exists which will work in direct boot + Preconditions.checkState(!mContext.getPackageManager().queryIntentServicesAsUser( + new Intent(FUSED_LOCATION_SERVICE_ACTION), + MATCH_DIRECT_BOOT_AWARE | MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM).isEmpty(), + "Unable to find a direct boot aware fused location provider"); + // bind to fused provider - LocationProviderProxy fusedProvider = LocationProviderProxy.createAndBind( + LocationProviderProxy fusedProvider = LocationProviderProxy.createAndRegister( mContext, FUSED_LOCATION_SERVICE_ACTION, com.android.internal.R.bool.config_enableFusedLocationOverlay, - com.android.internal.R.string.config_fusedLocationProviderPackageName, - com.android.internal.R.array.config_locationProviderPackageNames); + com.android.internal.R.string.config_fusedLocationProviderPackageName); if (fusedProvider != null) { LocationProviderManager fusedManager = new LocationProviderManager(FUSED_PROVIDER); mProviderManagers.add(fusedManager); @@ -674,47 +581,30 @@ public class LocationManagerService extends ILocationManager.Stub { } // bind to geocoder provider - mGeocodeProvider = GeocoderProxy.createAndBind(mContext, - com.android.internal.R.bool.config_enableGeocoderOverlay, - com.android.internal.R.string.config_geocoderProviderPackageName, - com.android.internal.R.array.config_locationProviderPackageNames); + mGeocodeProvider = GeocoderProxy.createAndRegister(mContext); if (mGeocodeProvider == null) { Slog.e(TAG, "no geocoder provider found"); } + // bind to geofence proxy if (mGnssManagerService != null) { - // bind to geofence provider - GeofenceProxy provider = GeofenceProxy.createAndBind( - mContext, com.android.internal.R.bool.config_enableGeofenceOverlay, - com.android.internal.R.string.config_geofenceProviderPackageName, - com.android.internal.R.array.config_locationProviderPackageNames, - mGnssManagerService.getGpsGeofenceProxy(), - null); - if (provider == null) { - Slog.d(TAG, "Unable to bind FLP Geofence proxy."); + IGpsGeofenceHardware gpsGeofenceHardware = mGnssManagerService.getGpsGeofenceProxy(); + if (gpsGeofenceHardware != null) { + GeofenceProxy provider = GeofenceProxy.createAndBind(mContext, gpsGeofenceHardware); + if (provider == null) { + Slog.d(TAG, "unable to bind to GeofenceProxy"); + } } } // bind to hardware activity recognition - boolean activityRecognitionHardwareIsSupported = ActivityRecognitionHardware.isSupported(); - ActivityRecognitionHardware activityRecognitionHardware = null; - if (activityRecognitionHardwareIsSupported) { - activityRecognitionHardware = ActivityRecognitionHardware.getInstance(mContext); - } else { - Slog.d(TAG, "Hardware Activity-Recognition not supported."); - } - ActivityRecognitionProxy proxy = ActivityRecognitionProxy.createAndBind( - mContext, - activityRecognitionHardwareIsSupported, - activityRecognitionHardware, - com.android.internal.R.bool.config_enableActivityRecognitionHardwareOverlay, - com.android.internal.R.string.config_activityRecognitionHardwarePackageName, - com.android.internal.R.array.config_locationProviderPackageNames); - if (proxy == null) { - Slog.d(TAG, "Unable to bind ActivityRecognitionProxy."); + HardwareActivityRecognitionProxy hardwareActivityRecognitionProxy = + HardwareActivityRecognitionProxy.createAndRegister(mContext); + if (hardwareActivityRecognitionProxy == null) { + Log.e(TAG, "unable to bind ActivityRecognitionProxy"); } - String[] testProviderStrings = resources.getStringArray( + String[] testProviderStrings = mContext.getResources().getStringArray( com.android.internal.R.array.config_testLocationProviders); for (String testProviderString : testProviderStrings) { String[] fragments = testProviderString.split(","); diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java index e8e3b39d5112..131a22b07c7b 100644 --- a/services/core/java/com/android/server/RescueParty.java +++ b/services/core/java/com/android/server/RescueParty.java @@ -18,6 +18,7 @@ package com.android.server; import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ContentResolver; import android.content.Context; @@ -25,14 +26,18 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.VersionedPackage; import android.os.Build; +import android.os.Bundle; import android.os.Environment; import android.os.FileUtils; import android.os.Process; import android.os.RecoverySystem; +import android.os.RemoteCallback; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; +import android.provider.DeviceConfig; import android.provider.Settings; +import android.util.ArraySet; import android.util.ExceptionUtils; import android.util.Log; import android.util.MathUtils; @@ -46,10 +51,16 @@ import com.android.server.PackageWatchdog.FailureReasons; import com.android.server.PackageWatchdog.PackageHealthObserver; import com.android.server.PackageWatchdog.PackageHealthObserverImpact; import com.android.server.am.SettingsToPropertiesMapper; -import com.android.server.utils.FlagNamespaceUtils; import java.io.File; +import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; /** * Utilities to help rescue the system from crash loops. Callers are expected to @@ -64,8 +75,6 @@ public class RescueParty { @VisibleForTesting static final String PROP_ENABLE_RESCUE = "persist.sys.enable_rescue"; @VisibleForTesting - static final int TRIGGER_COUNT = 5; - @VisibleForTesting static final String PROP_RESCUE_LEVEL = "sys.rescue_level"; @VisibleForTesting static final int LEVEL_NONE = 0; @@ -81,6 +90,8 @@ public class RescueParty { static final String PROP_RESCUE_BOOT_COUNT = "sys.rescue_boot_count"; @VisibleForTesting static final String TAG = "RescueParty"; + @VisibleForTesting + static final long DEFAULT_OBSERVING_DURATION_MS = TimeUnit.DAYS.toMillis(2); private static final String NAME = "rescue-party-observer"; @@ -139,7 +150,11 @@ public class RescueParty { */ public static void onSettingsProviderPublished(Context context) { handleNativeRescuePartyResets(); - executeRescueLevel(context); + executeRescueLevel(context, /*failedPackage=*/ null); + ContentResolver contentResolver = context.getContentResolver(); + Settings.Config.registerMonitorCallback(contentResolver, new RemoteCallback(result -> { + handleMonitorCallback(context, result); + })); } @VisibleForTesting @@ -147,10 +162,53 @@ public class RescueParty { return SystemClock.elapsedRealtime(); } + private static void handleMonitorCallback(Context context, Bundle result) { + String callbackType = result.getString(Settings.EXTRA_MONITOR_CALLBACK_TYPE, ""); + switch (callbackType) { + case Settings.EXTRA_NAMESPACE_UPDATED_CALLBACK: + String updatedNamespace = result.getString(Settings.EXTRA_NAMESPACE); + if (updatedNamespace != null) { + startObservingPackages(context, updatedNamespace); + } + break; + case Settings.EXTRA_ACCESS_CALLBACK: + String callingPackage = result.getString(Settings.EXTRA_CALLING_PACKAGE, null); + String namespace = result.getString(Settings.EXTRA_NAMESPACE, null); + if (namespace != null && callingPackage != null) { + RescuePartyObserver.getInstance(context).recordDeviceConfigAccess( + callingPackage, + namespace); + } + break; + default: + Slog.w(TAG, "Unrecognized DeviceConfig callback"); + break; + } + } + + private static void startObservingPackages(Context context, @NonNull String updatedNamespace) { + RescuePartyObserver rescuePartyObserver = RescuePartyObserver.getInstance(context); + Set<String> callingPackages = rescuePartyObserver.getCallingPackagesSet(updatedNamespace); + if (callingPackages == null) { + return; + } + List<String> callingPackageList = new ArrayList<>(); + callingPackageList.addAll(callingPackages); + Slog.i(TAG, "Starting to observe: " + callingPackageList + ", updated namespace: " + + updatedNamespace); + PackageWatchdog.getInstance(context).startObservingHealth( + rescuePartyObserver, + callingPackageList, + DEFAULT_OBSERVING_DURATION_MS); + } + private static void handleNativeRescuePartyResets() { if (SettingsToPropertiesMapper.isNativeFlagsResetPerformed()) { - FlagNamespaceUtils.resetDeviceConfig(Settings.RESET_MODE_TRUSTED_DEFAULTS, - Arrays.asList(SettingsToPropertiesMapper.getResetNativeCategories())); + String[] resetNativeCategories = SettingsToPropertiesMapper.getResetNativeCategories(); + for (int i = 0; i < resetNativeCategories.length; i++) { + DeviceConfig.resetToDefaults(Settings.RESET_MODE_TRUSTED_DEFAULTS, + resetNativeCategories[i]); + } } } @@ -164,7 +222,7 @@ public class RescueParty { /** * Escalate to the next rescue level. After incrementing the level you'll - * probably want to call {@link #executeRescueLevel(Context)}. + * probably want to call {@link #executeRescueLevel(Context, String)}. */ private static void incrementRescueLevel(int triggerUid) { final int level = MathUtils.constrain( @@ -177,13 +235,13 @@ public class RescueParty { + levelToString(level) + " triggered by UID " + triggerUid); } - private static void executeRescueLevel(Context context) { + private static void executeRescueLevel(Context context, @Nullable String failedPackage) { final int level = SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE); if (level == LEVEL_NONE) return; Slog.w(TAG, "Attempting rescue level " + levelToString(level)); try { - executeRescueLevelInternal(context, level); + executeRescueLevelInternal(context, level, failedPackage); EventLogTags.writeRescueSuccess(level); logCriticalInfo(Log.DEBUG, "Finished rescue level " + levelToString(level)); @@ -195,24 +253,23 @@ public class RescueParty { } } - private static void executeRescueLevelInternal(Context context, int level) throws Exception { + private static void executeRescueLevelInternal(Context context, int level, @Nullable + String failedPackage) throws Exception { StatsLog.write(StatsLog.RESCUE_PARTY_RESET_REPORTED, level); switch (level) { case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS: - resetAllSettings(context, Settings.RESET_MODE_UNTRUSTED_DEFAULTS); + resetAllSettings(context, Settings.RESET_MODE_UNTRUSTED_DEFAULTS, failedPackage); break; case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES: - resetAllSettings(context, Settings.RESET_MODE_UNTRUSTED_CHANGES); + resetAllSettings(context, Settings.RESET_MODE_UNTRUSTED_CHANGES, failedPackage); break; case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS: - resetAllSettings(context, Settings.RESET_MODE_TRUSTED_DEFAULTS); + resetAllSettings(context, Settings.RESET_MODE_TRUSTED_DEFAULTS, failedPackage); break; case LEVEL_FACTORY_RESET: RecoverySystem.rebootPromptAndWipeUserData(context, TAG); break; } - FlagNamespaceUtils.addToKnownResetNamespaces( - FlagNamespaceUtils.NAMESPACE_NO_PACKAGE); } private static int mapRescueLevelToUserImpact(int rescueLevel) { @@ -237,13 +294,14 @@ public class RescueParty { } } - private static void resetAllSettings(Context context, int mode) throws Exception { + private static void resetAllSettings(Context context, int mode, @Nullable String failedPackage) + throws Exception { // Try our best to reset all settings possible, and once finished // rethrow any exception that we encountered Exception res = null; final ContentResolver resolver = context.getContentResolver(); try { - FlagNamespaceUtils.resetDeviceConfig(mode); + resetDeviceConfig(context, mode, failedPackage); } catch (Exception e) { res = new RuntimeException("Failed to reset config settings", e); } @@ -264,6 +322,41 @@ public class RescueParty { } } + private static void resetDeviceConfig(Context context, int resetMode, + @Nullable String failedPackage) { + if (!shouldPerformScopedResets() || failedPackage == null) { + DeviceConfig.resetToDefaults(resetMode, /*namespace=*/ null); + } else { + performScopedReset(context, resetMode, failedPackage); + } + } + + private static boolean shouldPerformScopedResets() { + int rescueLevel = MathUtils.constrain( + SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE), + LEVEL_NONE, LEVEL_FACTORY_RESET); + return rescueLevel <= LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES; + } + + private static void performScopedReset(Context context, int resetMode, + @NonNull String failedPackage) { + RescuePartyObserver rescuePartyObserver = RescuePartyObserver.getInstance(context); + Set<String> affectedNamespaces = rescuePartyObserver.getAffectedNamespaceSet( + failedPackage); + if (affectedNamespaces == null) { + DeviceConfig.resetToDefaults(resetMode, /*namespace=*/ null); + } else { + Slog.w(TAG, + "Performing scoped reset for package: " + failedPackage + + ", affected namespaces: " + + Arrays.toString(affectedNamespaces.toArray())); + Iterator<String> it = affectedNamespaces.iterator(); + while (it.hasNext()) { + DeviceConfig.resetToDefaults(resetMode, it.next()); + } + } + } + /** * Handle mitigation action for package failures. This observer will be register to Package * Watchdog and will receive calls about package failures. This observer is persistent so it @@ -271,7 +364,9 @@ public class RescueParty { */ public static class RescuePartyObserver implements PackageHealthObserver { - private Context mContext; + private final Context mContext; + private final Map<String, Set<String>> mCallingPackageNamespaceSetMap = new HashMap<>(); + private final Map<String, Set<String>> mNamespaceCallingPackageSetMap = new HashMap<>(); @GuardedBy("RescuePartyObserver.class") static RescuePartyObserver sRescuePartyObserver; @@ -290,6 +385,13 @@ public class RescueParty { } } + @VisibleForTesting + static void reset() { + synchronized (RescuePartyObserver.class) { + sRescuePartyObserver = null; + } + } + @Override public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage, @FailureReasons int failureReason) { @@ -314,7 +416,8 @@ public class RescueParty { || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) { int triggerUid = getPackageUid(mContext, failedPackage.getPackageName()); incrementRescueLevel(triggerUid); - executeRescueLevel(mContext); + executeRescueLevel(mContext, + failedPackage == null ? null : failedPackage.getPackageName()); return true; } else { return false; @@ -355,7 +458,7 @@ public class RescueParty { return false; } incrementRescueLevel(Process.ROOT_UID); - executeRescueLevel(mContext); + executeRescueLevel(mContext, /*failedPackage=*/ null); return true; } @@ -363,6 +466,32 @@ public class RescueParty { public String getName() { return NAME; } + + private synchronized void recordDeviceConfigAccess(@NonNull String callingPackage, + @NonNull String namespace) { + // Record it in calling packages to namespace map + Set<String> namespaceSet = mCallingPackageNamespaceSetMap.get(callingPackage); + if (namespaceSet == null) { + namespaceSet = new ArraySet<>(); + mCallingPackageNamespaceSetMap.put(callingPackage, namespaceSet); + } + namespaceSet.add(namespace); + // Record it in namespace to calling packages map + Set<String> callingPackageSet = mNamespaceCallingPackageSetMap.get(namespace); + if (callingPackageSet == null) { + callingPackageSet = new ArraySet<>(); + } + callingPackageSet.add(callingPackage); + mNamespaceCallingPackageSetMap.put(namespace, callingPackageSet); + } + + private synchronized Set<String> getAffectedNamespaceSet(String failedPackage) { + return mCallingPackageNamespaceSetMap.get(failedPackage); + } + + private synchronized Set<String> getCallingPackagesSet(String namespace) { + return mNamespaceCallingPackageSetMap.get(namespace); + } } private static int[] getAllUserIds() { diff --git a/services/core/java/com/android/server/ServiceWatcher.java b/services/core/java/com/android/server/ServiceWatcher.java index 7f51aa9068fc..8564cb456ba6 100644 --- a/services/core/java/com/android/server/ServiceWatcher.java +++ b/services/core/java/com/android/server/ServiceWatcher.java @@ -16,7 +16,19 @@ package com.android.server; +import static android.content.Context.BIND_AUTO_CREATE; +import static android.content.Context.BIND_NOT_FOREGROUND; +import static android.content.Context.BIND_NOT_VISIBLE; +import static android.content.pm.PackageManager.GET_META_DATA; +import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO; +import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; +import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; +import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; + +import android.annotation.BoolRes; import android.annotation.Nullable; +import android.annotation.StringRes; +import android.annotation.UserIdInt; import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -24,11 +36,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; -import android.content.pm.Signature; import android.content.res.Resources; import android.os.Bundle; import android.os.Handler; @@ -37,15 +45,10 @@ import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; import android.util.Log; -import android.util.Slog; import com.android.internal.content.PackageMonitor; import com.android.internal.util.Preconditions; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.concurrent.Callable; @@ -55,16 +58,16 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; /** - * Find the best Service, and bind to it. - * Handles run-time package changes. + * Maintains a binding to the best service that matches the given intent information. Bind and + * unbind callbacks, as well as all binder operations, will all be run on the given handler. */ public class ServiceWatcher implements ServiceConnection { private static final String TAG = "ServiceWatcher"; - private static final boolean D = false; + private static final boolean D = Log.isLoggable(TAG, Log.DEBUG); - public static final String EXTRA_SERVICE_VERSION = "serviceVersion"; - public static final String EXTRA_SERVICE_IS_MULTIUSER = "serviceIsMultiuser"; + private static final String EXTRA_SERVICE_VERSION = "serviceVersion"; + private static final String EXTRA_SERVICE_IS_MULTIUSER = "serviceIsMultiuser"; private static final long BLOCKING_BINDER_TIMEOUT_MS = 30 * 1000; @@ -83,280 +86,300 @@ public class ServiceWatcher implements ServiceConnection { T run(IBinder binder) throws RemoteException; } - public static ArrayList<HashSet<Signature>> getSignatureSets(Context context, - String... packageNames) { - PackageManager pm = context.getPackageManager(); + /** + * Information on the service ServiceWatcher has selected as the best option for binding. + */ + public static final class ServiceInfo implements Comparable<ServiceInfo> { - ArrayList<HashSet<Signature>> signatureSets = new ArrayList<>(packageNames.length); - for (String packageName : packageNames) { - try { - Signature[] signatures = pm.getPackageInfo(packageName, - PackageManager.MATCH_SYSTEM_ONLY - | PackageManager.GET_SIGNATURES).signatures; - - HashSet<Signature> set = new HashSet<>(); - Collections.addAll(set, signatures); - signatureSets.add(set); - } catch (NameNotFoundException e) { - Log.w(TAG, packageName + " not found"); + public static final ServiceInfo NONE = new ServiceInfo(Integer.MIN_VALUE, null, + UserHandle.USER_NULL); + + public final int version; + @Nullable public final ComponentName component; + @UserIdInt public final int userId; + + private ServiceInfo(ResolveInfo resolveInfo, int currentUserId) { + Preconditions.checkArgument(resolveInfo.serviceInfo.getComponentName() != null); + + Bundle metadata = resolveInfo.serviceInfo.metaData; + boolean isMultiuser; + if (metadata != null) { + version = metadata.getInt(EXTRA_SERVICE_VERSION, Integer.MIN_VALUE); + isMultiuser = metadata.getBoolean(EXTRA_SERVICE_IS_MULTIUSER, false); + } else { + version = Integer.MIN_VALUE; + isMultiuser = false; } - } - return signatureSets; - } - /** Checks if signatures match. */ - public static boolean isSignatureMatch(Signature[] signatures, - List<HashSet<Signature>> sigSets) { - if (signatures == null) return false; + component = resolveInfo.serviceInfo.getComponentName(); + userId = isMultiuser ? UserHandle.USER_SYSTEM : currentUserId; + } - // build hashset of input to test against - HashSet<Signature> inputSet = new HashSet<>(); - Collections.addAll(inputSet, signatures); + private ServiceInfo(int version, @Nullable ComponentName component, int userId) { + Preconditions.checkArgument(component != null || version == Integer.MIN_VALUE); + this.version = version; + this.component = component; + this.userId = userId; + } - // test input against each of the signature sets - for (HashSet<Signature> referenceSet : sigSets) { - if (referenceSet.equals(inputSet)) { + @Override + public boolean equals(Object o) { + if (this == o) { return true; } + if (!(o instanceof ServiceInfo)) { + return false; + } + ServiceInfo that = (ServiceInfo) o; + return version == that.version && userId == that.userId + && Objects.equals(component, that.component); + } + + @Override + public int hashCode() { + return Objects.hash(version, component, userId); + } + + @Override + public int compareTo(ServiceInfo that) { + // ServiceInfos with higher version numbers always win (having a version number > + // MIN_VALUE implies having a non-null component). if version numbers are equal, a + // non-null component wins over a null component. if the version numbers are equal and + // both components exist then we prefer components that work for all users vs components + // that only work for a single user at a time. otherwise everything's equal. + int ret = Integer.compare(version, that.version); + if (ret == 0) { + if (component == null && that.component != null) { + ret = -1; + } else if (component != null && that.component == null) { + ret = 1; + } else { + if (userId != UserHandle.USER_SYSTEM && that.userId == UserHandle.USER_SYSTEM) { + ret = -1; + } else if (userId == UserHandle.USER_SYSTEM + && that.userId != UserHandle.USER_SYSTEM) { + ret = 1; + } + } + } + return ret; + } + + @Override + public String toString() { + return component + "@" + version + "[u" + userId + "]"; } - return false; } private final Context mContext; - private final String mTag; - private final String mAction; - private final String mServicePackageName; - private final List<HashSet<Signature>> mSignatureSets; - private final Handler mHandler; + private final Intent mIntent; + + @Nullable private final BinderRunner mOnBind; + @Nullable private final Runnable mOnUnbind; - // read/write from handler thread - private IBinder mBestService; + // read/write from handler thread only private int mCurrentUserId; - // read from any thread, write from handler thread - private volatile ComponentName mBestComponent; - private volatile int mBestVersion; - private volatile int mBestUserId; + // write from handler thread only, read anywhere + private volatile ServiceInfo mServiceInfo; - public ServiceWatcher(Context context, String logTag, String action, - int overlaySwitchResId, int defaultServicePackageNameResId, - int initialPackageNamesResId, Handler handler) { - Resources resources = context.getResources(); + // read/write from handler thread only + private IBinder mBinder; + public ServiceWatcher(Context context, Handler handler, String action, + @Nullable BinderRunner onBind, @Nullable Runnable onUnbind, + @BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId) { mContext = context; - mTag = logTag; - mAction = action; - - boolean enableOverlay = resources.getBoolean(overlaySwitchResId); - if (enableOverlay) { - String[] pkgs = resources.getStringArray(initialPackageNamesResId); - mServicePackageName = null; - mSignatureSets = getSignatureSets(context, pkgs); - if (D) Log.d(mTag, "Overlay enabled, packages=" + Arrays.toString(pkgs)); - } else { - mServicePackageName = resources.getString(defaultServicePackageNameResId); - mSignatureSets = getSignatureSets(context, mServicePackageName); - if (D) Log.d(mTag, "Overlay disabled, default package=" + mServicePackageName); + mHandler = FgThread.getHandler(); + mIntent = new Intent(Objects.requireNonNull(action)); + + Resources resources = context.getResources(); + boolean enableOverlay = resources.getBoolean(enableOverlayResId); + if (!enableOverlay) { + mIntent.setPackage(resources.getString(nonOverlayPackageResId)); } - mHandler = handler; + mOnBind = onBind; + mOnUnbind = onUnbind; - mBestComponent = null; - mBestVersion = Integer.MIN_VALUE; - mBestUserId = UserHandle.USER_NULL; + mCurrentUserId = UserHandle.USER_NULL; - mBestService = null; + mServiceInfo = ServiceInfo.NONE; + mBinder = null; } - protected void onBind() {} - - protected void onUnbind() {} - /** - * Start this watcher, including binding to the current best match and - * re-binding to any better matches down the road. - * <p> - * Note that if there are no matching encryption-aware services, we may not - * bind to a real service until after the current user is unlocked. - * - * @return {@code true} if a potential service implementation was found. + * Register this class, which will start the process of determining the best matching service + * and maintaining a binding to it. Will return false and fail if there are no possible matching + * services at the time this functions is called. */ - public final boolean start() { - // if we have to return false, do it before registering anything - if (isServiceMissing()) return false; - - // listen for relevant package changes if service overlay is enabled on handler - if (mServicePackageName == null) { - new PackageMonitor() { - @Override - public void onPackageUpdateFinished(String packageName, int uid) { - bindBestPackage(Objects.equals(packageName, getCurrentPackageName())); - } + public boolean register() { + if (mContext.getPackageManager().queryIntentServicesAsUser(mIntent, + MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE | MATCH_SYSTEM_ONLY, + UserHandle.USER_SYSTEM).isEmpty()) { + return false; + } - @Override - public void onPackageAdded(String packageName, int uid) { - bindBestPackage(Objects.equals(packageName, getCurrentPackageName())); - } + new PackageMonitor() { + @Override + public void onPackageUpdateFinished(String packageName, int uid) { + ServiceWatcher.this.onPackageChanged(packageName); + } - @Override - public void onPackageRemoved(String packageName, int uid) { - bindBestPackage(Objects.equals(packageName, getCurrentPackageName())); - } + @Override + public void onPackageAdded(String packageName, int uid) { + ServiceWatcher.this.onPackageChanged(packageName); + } - @Override - public boolean onPackageChanged(String packageName, int uid, String[] components) { - bindBestPackage(Objects.equals(packageName, getCurrentPackageName())); - return super.onPackageChanged(packageName, uid, components); - } - }.register(mContext, UserHandle.ALL, true, mHandler); - } + @Override + public void onPackageRemoved(String packageName, int uid) { + ServiceWatcher.this.onPackageChanged(packageName); + } + + @Override + public boolean onPackageChanged(String packageName, int uid, String[] components) { + ServiceWatcher.this.onPackageChanged(packageName); + return super.onPackageChanged(packageName, uid, components); + } + }.register(mContext, UserHandle.ALL, true, mHandler); - // listen for user change on handler IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_USER_SWITCHED); intentFilter.addAction(Intent.ACTION_USER_UNLOCKED); mContext.registerReceiverAsUser(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, - UserHandle.USER_NULL); - if (Intent.ACTION_USER_SWITCHED.equals(action)) { - mCurrentUserId = userId; - bindBestPackage(false); - } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) { - if (userId == mCurrentUserId) { - bindBestPackage(false); - } + String action = intent.getAction(); + if (action == null) { + return; + } + int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); + if (userId == UserHandle.USER_NULL) { + return; } + + switch (action) { + case Intent.ACTION_USER_SWITCHED: + onUserSwitched(userId); + break; + case Intent.ACTION_USER_UNLOCKED: + onUserUnlocked(userId); + break; + default: + break; + } + } }, UserHandle.ALL, intentFilter, null, mHandler); mCurrentUserId = ActivityManager.getCurrentUser(); - mHandler.post(() -> bindBestPackage(false)); + mHandler.post(() -> onBestServiceChanged(false)); return true; } - /** Returns the name of the currently connected package or null. */ - @Nullable - public String getCurrentPackageName() { - ComponentName bestComponent = mBestComponent; - return bestComponent == null ? null : bestComponent.getPackageName(); - } - - private boolean isServiceMissing() { - return mContext.getPackageManager().queryIntentServicesAsUser(new Intent(mAction), - PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, - UserHandle.USER_SYSTEM).isEmpty(); + /** + * Returns information on the currently selected service. + */ + public ServiceInfo getBoundService() { + return mServiceInfo; } - private void bindBestPackage(boolean forceRebind) { + private void onBestServiceChanged(boolean forceRebind) { Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); - Intent intent = new Intent(mAction); - if (mServicePackageName != null) { - intent.setPackage(mServicePackageName); - } - - List<ResolveInfo> rInfos = mContext.getPackageManager().queryIntentServicesAsUser(intent, - PackageManager.GET_META_DATA | PackageManager.MATCH_DIRECT_BOOT_AUTO, + List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentServicesAsUser( + mIntent, + GET_META_DATA | MATCH_DIRECT_BOOT_AUTO | MATCH_SYSTEM_ONLY, mCurrentUserId); - if (rInfos == null) { - rInfos = Collections.emptyList(); + + ServiceInfo bestServiceInfo = ServiceInfo.NONE; + for (ResolveInfo resolveInfo : resolveInfos) { + ServiceInfo serviceInfo = new ServiceInfo(resolveInfo, mCurrentUserId); + if (serviceInfo.compareTo(bestServiceInfo) > 0) { + bestServiceInfo = serviceInfo; + } } - ComponentName bestComponent = null; - int bestVersion = Integer.MIN_VALUE; - boolean bestIsMultiuser = false; + if (forceRebind || !bestServiceInfo.equals(mServiceInfo)) { + rebind(bestServiceInfo); + } + } - for (ResolveInfo rInfo : rInfos) { - ComponentName component = rInfo.serviceInfo.getComponentName(); - String packageName = component.getPackageName(); + private void rebind(ServiceInfo newServiceInfo) { + Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); - // check signature - try { - PackageInfo pInfo = mContext.getPackageManager().getPackageInfo(packageName, - PackageManager.GET_SIGNATURES - | PackageManager.MATCH_DIRECT_BOOT_AUTO); - if (!isSignatureMatch(pInfo.signatures, mSignatureSets)) { - Log.w(mTag, packageName + " resolves service " + mAction - + ", but has wrong signature, ignoring"); - continue; - } - } catch (NameNotFoundException e) { - Log.wtf(mTag, e); - continue; + if (!mServiceInfo.equals(ServiceInfo.NONE)) { + if (D) { + Log.i(TAG, "[" + mIntent.getAction() + "] unbinding from " + mServiceInfo); } - // check metadata - Bundle metadata = rInfo.serviceInfo.metaData; - int version = Integer.MIN_VALUE; - boolean isMultiuser = false; - if (metadata != null) { - version = metadata.getInt(EXTRA_SERVICE_VERSION, Integer.MIN_VALUE); - isMultiuser = metadata.getBoolean(EXTRA_SERVICE_IS_MULTIUSER, false); - } + mContext.unbindService(this); + mServiceInfo = ServiceInfo.NONE; + } - if (version > bestVersion) { - bestComponent = component; - bestVersion = version; - bestIsMultiuser = isMultiuser; - } + mServiceInfo = newServiceInfo; + if (mServiceInfo.equals(ServiceInfo.NONE)) { + return; } + Preconditions.checkState(mServiceInfo.component != null); + if (D) { - Log.d(mTag, String.format("bindBestPackage for %s : %s found %d, %s", mAction, - (mServicePackageName == null ? "" - : "(" + mServicePackageName + ") "), rInfos.size(), - (bestComponent == null ? "no new best component" - : "new best component: " + bestComponent))); + Log.i(TAG, getLogPrefix() + " binding to " + mServiceInfo); } - if (bestComponent == null) { - Slog.w(mTag, "Odd, no component found for service " + mAction); - unbind(); - return; + Intent bindIntent = new Intent(mIntent).setComponent(mServiceInfo.component); + mContext.bindServiceAsUser(bindIntent, this, + BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_NOT_VISIBLE, + mHandler, UserHandle.of(mServiceInfo.userId)); + } + + @Override + public final void onServiceConnected(ComponentName component, IBinder binder) { + Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); + + if (D) { + Log.i(TAG, getLogPrefix() + " connected to " + component); } - int userId = bestIsMultiuser ? UserHandle.USER_SYSTEM : mCurrentUserId; - boolean alreadyBound = Objects.equals(bestComponent, mBestComponent) - && bestVersion == mBestVersion && userId == mBestUserId; - if (forceRebind || !alreadyBound) { - unbind(); - bind(bestComponent, bestVersion, userId); + mBinder = binder; + if (mOnBind != null) { + runOnBinder(mOnBind); } } - private void bind(ComponentName component, int version, int userId) { + @Override + public final void onServiceDisconnected(ComponentName component) { Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); - Intent intent = new Intent(mAction); - intent.setComponent(component); - - mBestComponent = component; - mBestVersion = version; - mBestUserId = userId; + if (D) { + Log.i(TAG, getLogPrefix() + " disconnected from " + component); + } - if (D) Log.d(mTag, "binding " + component + " (v" + version + ") (u" + userId + ")"); - mContext.bindServiceAsUser(intent, this, - Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND | Context.BIND_NOT_VISIBLE, - UserHandle.of(userId)); + mBinder = null; + if (mOnUnbind != null) { + mOnUnbind.run(); + } } - private void unbind() { - Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); + private void onUserSwitched(@UserIdInt int userId) { + mCurrentUserId = userId; + onBestServiceChanged(false); + } - if (mBestComponent != null) { - if (D) Log.d(mTag, "unbinding " + mBestComponent); - mContext.unbindService(this); + private void onUserUnlocked(@UserIdInt int userId) { + if (userId == mCurrentUserId) { + onBestServiceChanged(false); } + } - mBestComponent = null; - mBestVersion = Integer.MIN_VALUE; - mBestUserId = UserHandle.USER_NULL; + private void onPackageChanged(String packageName) { + // force a rebind if the changed package was the currently connected package + String currentPackageName = + mServiceInfo.component != null ? mServiceInfo.component.getPackageName() : null; + onBestServiceChanged(packageName.equals(currentPackageName)); } /** @@ -365,26 +388,26 @@ public class ServiceWatcher implements ServiceConnection { */ public final void runOnBinder(BinderRunner runner) { runOnHandler(() -> { - if (mBestService == null) { + if (mBinder == null) { return; } try { - runner.run(mBestService); - } catch (RuntimeException e) { - // the code being run is privileged, but may be outside the system server, and thus - // we cannot allow runtime exceptions to crash the system server - Log.e(TAG, "exception while while running " + runner + " on " + mBestService - + " from " + this, e); - } catch (RemoteException e) { - // do nothing + runner.run(mBinder); + } catch (RuntimeException | RemoteException e) { + // binders may propagate some specific non-RemoteExceptions from the other side + // through the binder as well - we cannot allow those to crash the system server + Log.e(TAG, getLogPrefix() + " exception running on " + mServiceInfo, e); } }); } /** * Runs the given function synchronously if currently connected, and returns the default value - * if not currently connected or if any exception is thrown. + * if not currently connected or if any exception is thrown. Do not obtain any locks within the + * BlockingBinderRunner, or risk deadlock. The default value will be returned if there is no + * service connection when this is run, if a RemoteException occurs, or if the operation times + * out. * * @deprecated Using this function is an indication that your AIDL API is broken. Calls from * system server to outside MUST be one-way, and so cannot return any result, and this @@ -395,13 +418,16 @@ public class ServiceWatcher implements ServiceConnection { public final <T> T runOnBinderBlocking(BlockingBinderRunner<T> runner, T defaultValue) { try { return runOnHandlerBlocking(() -> { - if (mBestService == null) { + if (mBinder == null) { return defaultValue; } try { - return runner.run(mBestService); - } catch (RemoteException e) { + return runner.run(mBinder); + } catch (RuntimeException | RemoteException e) { + // binders may propagate some specific non-RemoteExceptions from the other side + // through the binder as well - we cannot allow those to crash the system server + Log.e(TAG, getLogPrefix() + " exception running on " + mServiceInfo, e); return defaultValue; } }); @@ -410,30 +436,6 @@ public class ServiceWatcher implements ServiceConnection { } } - @Override - public final void onServiceConnected(ComponentName component, IBinder binder) { - runOnHandler(() -> { - if (D) Log.d(mTag, component + " connected"); - mBestService = binder; - onBind(); - }); - } - - @Override - public final void onServiceDisconnected(ComponentName component) { - runOnHandler(() -> { - if (D) Log.d(mTag, component + " disconnected"); - mBestService = null; - onUnbind(); - }); - } - - @Override - public String toString() { - ComponentName bestComponent = mBestComponent; - return bestComponent == null ? "null" : bestComponent.toShortString() + "@" + mBestVersion; - } - private void runOnHandler(Runnable r) { if (Looper.myLooper() == mHandler.getLooper()) { r.run(); @@ -467,4 +469,8 @@ public class ServiceWatcher implements ServiceConnection { } } } + + private String getLogPrefix() { + return "[" + mIntent.getAction() + "]"; + } } diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 32830aeacf06..4b4ce348385a 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -1701,8 +1701,15 @@ class StorageManagerService extends IStorageManager.Stub if (mIsFuseEnabled != settingsFuseFlag) { Slog.i(TAG, "Toggling persist.sys.fuse to " + settingsFuseFlag); SystemProperties.set(PROP_FUSE, Boolean.toString(settingsFuseFlag)); - // Perform hard reboot to kick policy into place - mContext.getSystemService(PowerManager.class).reboot("fuse_prop"); + + PowerManager powerManager = mContext.getSystemService(PowerManager.class); + if (powerManager.isRebootingUserspaceSupported()) { + // Perform userspace reboot to kick policy into place + powerManager.reboot(PowerManager.REBOOT_USERSPACE); + } else { + // Perform hard reboot to kick policy into place + powerManager.reboot("fuse_prop"); + } } } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 3ffa5dea4d89..ac85bf57e9b0 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -31,6 +31,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SERVICE_E import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.ActivityThread; @@ -2068,9 +2069,9 @@ public final class ActiveServices { if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "retrieveServiceLocked: " + service + " type=" + resolvedType + " callingUid=" + callingUid); - userId = mAm.mUserController.handleIncomingUser(callingPid, callingUid, userId, false, - ActivityManagerInternal.ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE, "service", - callingPackage); + userId = mAm.mUserController.handleIncomingUser(callingPid, callingUid, userId, + /* allowAll= */false, getAllowMode(service, callingPackage), + /* name= */ "service", callingPackage); ServiceMap smap = getServiceMapLocked(userId); final ComponentName comp; @@ -2260,6 +2261,17 @@ public final class ActiveServices { return null; } + private int getAllowMode(Intent service, @Nullable String callingPackage) { + if (callingPackage == null || service.getComponent() == null) { + return ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE; + } + if (callingPackage.equals(service.getComponent().getPackageName())) { + return ActivityManagerInternal.ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE; + } else { + return ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE; + } + } + private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) { if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, ">>> EXECUTING " + why + " of " + r + " in app " + r.app); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 00c0b3e88517..5596b2fcb762 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -214,6 +214,7 @@ import android.content.pm.PackageParser; import android.content.pm.ParceledListSlice; import android.content.pm.PathPermission; import android.content.pm.PermissionInfo; +import android.content.pm.ProcessInfo; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.SELinuxUtil; @@ -751,67 +752,23 @@ public class ActivityManagerService extends IActivityManager.Stub } /** + * These are the currently running processes for which we have a ProcessInfo. + * Note: needs to be static since the permission checking call chain is static. This + * all probably should be refactored into a separate permission checking object. + */ + @GuardedBy("sActiveProcessInfoSelfLocked") + static final SparseArray<ProcessInfo> sActiveProcessInfoSelfLocked = new SparseArray<>(); + + /** * All of the processes we currently have running organized by pid. * The keys are the pid running the application. * * <p>NOTE: This object is protected by its own lock, NOT the global activity manager lock! */ final PidMap mPidsSelfLocked = new PidMap(); - final class PidMap { + static final class PidMap { private final SparseArray<ProcessRecord> mPidMap = new SparseArray<>(); - /** - * Puts the process record in the map. - * <p>NOTE: Callers should avoid acquiring the mPidsSelfLocked lock before calling this - * method. - */ - void put(ProcessRecord app) { - synchronized (this) { - mPidMap.put(app.pid, app); - } - mAtmInternal.onProcessMapped(app.pid, app.getWindowProcessController()); - } - - /** - * Removes the process record from the map. - * <p>NOTE: Callers should avoid acquiring the mPidsSelfLocked lock before calling this - * method. - */ - void remove(ProcessRecord app) { - boolean removed = false; - synchronized (this) { - final ProcessRecord existingApp = mPidMap.get(app.pid); - if (existingApp != null && existingApp.startSeq == app.startSeq) { - mPidMap.remove(app.pid); - removed = true; - } - } - if (removed) { - mAtmInternal.onProcessUnMapped(app.pid); - } - } - - /** - * Removes the process record from the map if it has a thread. - * <p>NOTE: Callers should avoid acquiring the mPidsSelfLocked lock before calling this - * method. - */ - boolean removeIfNoThread(ProcessRecord app) { - boolean removed = false; - synchronized (this) { - final ProcessRecord existingApp = get(app.pid); - if (existingApp != null && existingApp.startSeq == app.startSeq - && app.thread == null) { - mPidMap.remove(app.pid); - removed = true; - } - } - if (removed) { - mAtmInternal.onProcessUnMapped(app.pid); - } - return removed; - } - ProcessRecord get(int pid) { return mPidMap.get(pid); } @@ -831,6 +788,82 @@ public class ActivityManagerService extends IActivityManager.Stub int indexOfKey(int key) { return mPidMap.indexOfKey(key); } + + void doAddInternal(ProcessRecord app) { + mPidMap.put(app.pid, app); + } + + boolean doRemoveInternal(ProcessRecord app) { + final ProcessRecord existingApp = mPidMap.get(app.pid); + if (existingApp != null && existingApp.startSeq == app.startSeq) { + mPidMap.remove(app.pid); + return true; + } + return false; + } + + boolean doRemoveIfNoThreadInternal(ProcessRecord app) { + if (app == null || app.thread != null) { + return false; + } + return doRemoveInternal(app); + } + } + + /** + * Puts the process record in the map. + * <p>NOTE: Callers should avoid acquiring the mPidsSelfLocked lock before calling this + * method. + */ + void addPidLocked(ProcessRecord app) { + synchronized (mPidsSelfLocked) { + mPidsSelfLocked.doAddInternal(app); + } + synchronized (sActiveProcessInfoSelfLocked) { + if (app.processInfo != null) { + sActiveProcessInfoSelfLocked.put(app.pid, app.processInfo); + } else { + sActiveProcessInfoSelfLocked.remove(app.pid); + } + } + mAtmInternal.onProcessMapped(app.pid, app.getWindowProcessController()); + } + + /** + * Removes the process record from the map. + * <p>NOTE: Callers should avoid acquiring the mPidsSelfLocked lock before calling this + * method. + */ + void removePidLocked(ProcessRecord app) { + final boolean removed; + synchronized (mPidsSelfLocked) { + removed = mPidsSelfLocked.doRemoveInternal(app); + } + if (removed) { + synchronized (sActiveProcessInfoSelfLocked) { + sActiveProcessInfoSelfLocked.remove(app.pid); + } + mAtmInternal.onProcessUnMapped(app.pid); + } + } + + /** + * Removes the process record from the map if it doesn't have a thread. + * <p>NOTE: Callers should avoid acquiring the mPidsSelfLocked lock before calling this + * method. + */ + boolean removePidIfNoThread(ProcessRecord app) { + final boolean removed; + synchronized (mPidsSelfLocked) { + removed = mPidsSelfLocked.doRemoveIfNoThreadInternal(app); + } + if (removed) { + synchronized (sActiveProcessInfoSelfLocked) { + sActiveProcessInfoSelfLocked.remove(app.pid); + } + mAtmInternal.onProcessUnMapped(app.pid); + } + return removed; } /** @@ -2061,7 +2094,7 @@ public class ActivityManagerService extends IActivityManager.Stub app.getWindowProcessController().setPid(MY_PID); app.maxAdj = ProcessList.SYSTEM_ADJ; app.makeActive(mSystemThread.getApplicationThread(), mProcessStats); - mPidsSelfLocked.put(app); + addPidLocked(app); mProcessList.updateLruProcessLocked(app, false, null); updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); } @@ -4723,7 +4756,7 @@ public class ActivityManagerService extends IActivityManager.Stub @GuardedBy("this") private final void processStartTimedOutLocked(ProcessRecord app) { final int pid = app.pid; - boolean gone = mPidsSelfLocked.removeIfNoThread(app); + boolean gone = removePidIfNoThread(app); if (gone) { Slog.w(TAG, "Process " + app + " failed to attach"); @@ -4796,7 +4829,7 @@ public class ActivityManagerService extends IActivityManager.Stub // If there is already an app occupying that pid that hasn't been cleaned up cleanUpApplicationRecordLocked(app, false, false, -1, true /*replacingPid*/); - mPidsSelfLocked.remove(app); + removePidLocked(app); app = null; } } else { @@ -5896,6 +5929,21 @@ public class ActivityManagerService extends IActivityManager.Stub if (pid == MY_PID) { return PackageManager.PERMISSION_GRANTED; } + // If there is an explicit permission being checked, and this is coming from a process + // that has been denied access to that permission, then just deny. Ultimately this may + // not be quite right -- it means that even if the caller would have access for another + // reason (such as being the owner of the component it is trying to access), it would still + // fail. This also means the system and root uids would be able to deny themselves + // access to permissions, which... well okay. ¯\_(ツ)_/¯ + if (permission != null) { + synchronized (sActiveProcessInfoSelfLocked) { + ProcessInfo procInfo = sActiveProcessInfoSelfLocked.get(pid); + if (procInfo != null && procInfo.deniedPermissions != null + && procInfo.deniedPermissions.contains(permission)) { + return PackageManager.PERMISSION_DENIED; + } + } + } return ActivityManager.checkComponentPermission(permission, uid, owningUid, exported); } @@ -14367,7 +14415,7 @@ public class ActivityManagerService extends IActivityManager.Stub return true; } else if (app.pid > 0 && app.pid != MY_PID) { // Goodbye! - mPidsSelfLocked.remove(app); + removePidLocked(app); mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app); mBatteryStatsService.noteProcessFinish(app.processName, app.info.uid); if (app.isolated) { diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 3f0e2ce9ed13..53a967b0ce50 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -2391,6 +2391,8 @@ final class ActivityManagerShellCommand extends ShellCommand { return UsageStatsManager.STANDBY_BUCKET_FREQUENT; } else if (lower.startsWith("ra")) { return UsageStatsManager.STANDBY_BUCKET_RARE; + } else if (lower.startsWith("re")) { + return UsageStatsManager.STANDBY_BUCKET_RESTRICTED; } else if (lower.startsWith("ne")) { return UsageStatsManager.STANDBY_BUCKET_NEVER; } else { diff --git a/services/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java index 6e135d65f127..bab133f58c11 100644 --- a/services/core/java/com/android/server/am/AppExitInfoTracker.java +++ b/services/core/java/com/android/server/am/AppExitInfoTracker.java @@ -189,12 +189,14 @@ public final class AppExitInfoTracker { } void onSystemReady() { - // Read the sysprop set by lmkd and set this to persist so app could read it. - SystemProperties.set("persist.sys.lmk.reportkills", - Boolean.toString(SystemProperties.getBoolean("sys.lmk.reportkills", false))); registerForUserRemoval(); registerForPackageRemoval(); - IoThread.getHandler().post(this::loadExistingProcessExitInfo); + IoThread.getHandler().post(() -> { + // Read the sysprop set by lmkd and set this to persist so app could read it. + SystemProperties.set("persist.sys.lmk.reportkills", + Boolean.toString(SystemProperties.getBoolean("sys.lmk.reportkills", false))); + loadExistingProcessExitInfo(); + }); } @GuardedBy("mService") diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java index fa55701cd882..a03f0bb4e399 100644 --- a/services/core/java/com/android/server/am/CoreSettingsObserver.java +++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java @@ -108,6 +108,21 @@ final class CoreSettingsObserver extends ContentObserver { sDeviceConfigEntries.add(new DeviceConfigEntry( DeviceConfig.NAMESPACE_WIDGET, WidgetFlags.ENABLE_CURSOR_CONTROL, WidgetFlags.KEY_ENABLE_CURSOR_CONTROL, boolean.class)); + sDeviceConfigEntries.add(new DeviceConfigEntry( + DeviceConfig.NAMESPACE_WIDGET, WidgetFlags.INSERTION_HANDLE_DELTA_HEIGHT, + WidgetFlags.KEY_INSERTION_HANDLE_DELTA_HEIGHT, int.class)); + sDeviceConfigEntries.add(new DeviceConfigEntry( + DeviceConfig.NAMESPACE_WIDGET, WidgetFlags.INSERTION_HANDLE_OPACITY, + WidgetFlags.KEY_INSERTION_HANDLE_OPACITY, int.class)); + sDeviceConfigEntries.add(new DeviceConfigEntry( + DeviceConfig.NAMESPACE_WIDGET, WidgetFlags.ENABLE_NEW_MAGNIFIER, + WidgetFlags.KEY_ENABLE_NEW_MAGNIFIER, boolean.class)); + sDeviceConfigEntries.add(new DeviceConfigEntry( + DeviceConfig.NAMESPACE_WIDGET, WidgetFlags.MAGNIFIER_ZOOM_FACTOR, + WidgetFlags.KEY_MAGNIFIER_ZOOM_FACTOR, float.class)); + sDeviceConfigEntries.add(new DeviceConfigEntry( + DeviceConfig.NAMESPACE_WIDGET, WidgetFlags.MAGNIFIER_ASPECT_RATIO, + WidgetFlags.KEY_MAGNIFIER_ASPECT_RATIO, float.class)); // add other device configs here... } diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 63331fab4aef..04297828ebfc 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -125,7 +125,7 @@ public final class OomAdjuster { static final String OOM_ADJ_REASON_PROCESS_END = OOM_ADJ_REASON_METHOD + "_processEnd"; /** - * Flag {@link Context#BIND_INCLUDE_CAPABILITIES} is used + * Flag {@link android.content.Context#BIND_INCLUDE_CAPABILITIES} is used * to pass while-in-use capabilities from client process to bound service. In targetSdkVersion * R and above, if client is a TOP activity, when this flag is present, bound service gets all * while-in-use capabilities; when this flag is not present, bound service gets no while-in-use diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 4a8984524bb7..38cb501111ca 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -1564,7 +1564,7 @@ public final class ProcessList { long startTime = SystemClock.uptimeMillis(); if (app.pid > 0 && app.pid != ActivityManagerService.MY_PID) { checkSlow(startTime, "startProcess: removing from pids map"); - mService.mPidsSelfLocked.remove(app); + mService.removePidLocked(app); mService.mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app); checkSlow(startTime, "startProcess: done removing from pids map"); app.setPid(0); @@ -1609,10 +1609,28 @@ public final class ProcessList { } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } + + // Remove any gids needed if the process has been denied permissions. + // NOTE: eventually we should probably have the package manager pre-compute + // this for us? + if (app.processInfo != null && app.processInfo.deniedPermissions != null) { + for (int i = app.processInfo.deniedPermissions.size() - 1; i >= 0; i--) { + int[] denyGids = mService.mPackageManagerInt.getPermissionGids( + app.processInfo.deniedPermissions.valueAt(i), app.userId); + if (denyGids != null) { + for (int gid : denyGids) { + permGids = ArrayUtils.removeInt(permGids, gid); + } + } + } + } + int numGids = 3; - if (mountExternal == Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE) { + if (mountExternal == Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE + || app.info.packageName.equals("com.android.externalstorage")) { numGids++; } + /* * Add shared application and profile GIDs so applications can share some * resources like shared libraries and access user-wide resources @@ -1626,8 +1644,14 @@ public final class ProcessList { gids[0] = UserHandle.getSharedAppGid(UserHandle.getAppId(uid)); gids[1] = UserHandle.getCacheAppGid(UserHandle.getAppId(uid)); gids[2] = UserHandle.getUserGid(UserHandle.getUserId(uid)); + if (numGids > 3) { - gids[3] = Process.SDCARD_RW_GID; + if (app.info.packageName.equals("com.android.externalstorage")) { + // Allows access to 'unreliable' (USB OTG) volumes via SAF + gids[3] = Process.MEDIA_RW_GID; + } else if (mountExternal == Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE) { + gids[3] = Process.SDCARD_RW_GID; + } } // Replace any invalid GIDs @@ -2311,7 +2335,7 @@ public final class ProcessList { mService.cleanUpApplicationRecordLocked(oldApp, false, false, -1, true /*replacingPid*/); } - mService.mPidsSelfLocked.put(app); + mService.addPidLocked(app); synchronized (mService.mPidsSelfLocked) { if (!procAttached) { Message msg = mService.mHandler.obtainMessage(PROC_START_TIMEOUT_MSG); @@ -2492,7 +2516,7 @@ public final class ProcessList { .pendingStart)) { int pid = app.pid; if (pid > 0) { - mService.mPidsSelfLocked.remove(app); + mService.removePidLocked(app); mService.mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app); mService.mBatteryStatsService.noteProcessFinish(app.processName, app.info.uid); if (app.isolated) { @@ -3945,4 +3969,3 @@ public final class ProcessList { } }; } - diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 156466cee970..0e1e0f9f64f1 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -35,6 +35,7 @@ import android.app.IApplicationThread; import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; +import android.content.pm.ProcessInfo; import android.content.pm.ServiceInfo; import android.content.pm.VersionedPackage; import android.content.res.CompatibilityInfo; @@ -84,6 +85,7 @@ class ProcessRecord implements WindowProcessListener { private final ActivityManagerService mService; // where we came from final ApplicationInfo info; // all about the first app in the process + final ProcessInfo processInfo; // if non-null, process-specific manifest info final boolean isolated; // true if this is a special isolated process final boolean appZygote; // true if this is forked from the app zygote final int uid; // uid of process; may be different from 'info' if isolated @@ -603,6 +605,13 @@ class ProcessRecord implements WindowProcessListener { int _uid) { mService = _service; info = _info; + if (_service.mPackageManagerInt != null) { + ArrayMap<String, ProcessInfo> processes = + _service.mPackageManagerInt.getProcessesForUid(_uid); + processInfo = processes != null ? processes.get(_processName) : null; + } else { + processInfo = null; + } isolated = _info.uid != _uid; appZygote = (UserHandle.getAppId(_uid) >= Process.FIRST_APP_ZYGOTE_ISOLATED_UID && UserHandle.getAppId(_uid) <= Process.LAST_APP_ZYGOTE_ISOLATED_UID); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 46972d90ce3b..8f6bd212da19 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -21,6 +21,7 @@ import static android.media.AudioManager.RINGER_MODE_NORMAL; import static android.media.AudioManager.RINGER_MODE_SILENT; import static android.media.AudioManager.RINGER_MODE_VIBRATE; import static android.media.AudioManager.STREAM_SYSTEM; +import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE; import static android.os.Process.FIRST_APPLICATION_UID; import static android.provider.Settings.Secure.VOLUME_HUSH_MUTE; import static android.provider.Settings.Secure.VOLUME_HUSH_OFF; @@ -89,6 +90,7 @@ import android.media.PlayerBase; import android.media.VolumePolicy; import android.media.audiofx.AudioEffect; import android.media.audiopolicy.AudioMix; +import android.media.audiopolicy.AudioMixingRule.AudioMixMatchCriterion; import android.media.audiopolicy.AudioPolicy; import android.media.audiopolicy.AudioPolicyConfig; import android.media.audiopolicy.AudioProductStrategy; @@ -6774,6 +6776,7 @@ public class AudioService extends IAudioService.Stub boolean requireValidProjection = false; boolean requireCaptureAudioOrMediaOutputPerm = false; + boolean requireVoiceComunicationOutputPerm = false; boolean requireModifyRouting = false; if (hasFocusAccess || isVolumeController) { @@ -6783,8 +6786,15 @@ public class AudioService extends IAudioService.Stub requireModifyRouting |= true; } for (AudioMix mix : policyConfig.getMixes()) { - // If mix is requesting a privileged capture - if (mix.getRule().allowPrivilegedPlaybackCapture()) { + // If mix is trying to capture USAGE_VOICE_COMMUNICATION using playback capture + if (isVoiceCommunicationPlaybackCaptureMix(mix)) { + // then it must have CAPTURE_USAGE_VOICE_COMMUNICATION_OUTPUT permission + requireVoiceComunicationOutputPerm |= true; + } + // If mix is requesting privileged capture and is capturing at + // least one usage which is not USAGE_VOICE_COMMUNICATION. + if (mix.getRule().allowPrivilegedPlaybackCapture() + && isNonVoiceCommunicationCaptureMix(mix)) { // then it must have CAPTURE_MEDIA_OUTPUT or CAPTURE_AUDIO_OUTPUT permission requireCaptureAudioOrMediaOutputPerm |= true; // and its format must be low quality enough @@ -6812,6 +6822,14 @@ public class AudioService extends IAudioService.Stub return false; } + if (requireVoiceComunicationOutputPerm + && !callerHasPermission( + android.Manifest.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT)) { + Log.e(TAG, "Privileged audio capture for voice communication requires " + + "CAPTURE_VOICE_COMMUNICATION_OUTPUT system permission"); + return false; + } + if (requireValidProjection && !canProjectAudio(projection)) { return false; } @@ -6825,6 +6843,41 @@ public class AudioService extends IAudioService.Stub return true; } + /** + * Checks whether a given AudioMix is used for playback capture + * (has the ROUTE_FLAG_LOOP_BACK_RENDER flag) and has a matching + * criterion for USAGE_VOICE_COMMUNICATION. + */ + private boolean isVoiceCommunicationPlaybackCaptureMix(AudioMix mix) { + if (mix.getRouteFlags() != mix.ROUTE_FLAG_LOOP_BACK_RENDER) { + return false; + } + + for (AudioMixMatchCriterion criterion : mix.getRule().getCriteria()) { + if (criterion.getRule() == RULE_MATCH_ATTRIBUTE_USAGE + && criterion.getAudioAttributes().getUsage() + == AudioAttributes.USAGE_VOICE_COMMUNICATION) { + return true; + } + } + return false; + } + + /** + * Checks whether a given AudioMix has a matching + * criterion for a usage which is not USAGE_VOICE_COMMUNICATION. + */ + private boolean isNonVoiceCommunicationCaptureMix(AudioMix mix) { + for (AudioMixMatchCriterion criterion : mix.getRule().getCriteria()) { + if (criterion.getRule() == RULE_MATCH_ATTRIBUTE_USAGE + && criterion.getAudioAttributes().getUsage() + != AudioAttributes.USAGE_VOICE_COMMUNICATION) { + return true; + } + } + return false; + } + private boolean callerHasPermission(String permission) { return mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED; } @@ -6988,6 +7041,26 @@ public class AudioService extends IAudioService.Stub } } + /** see AudioPolicy.setUserIdDeviceAffinity() */ + public int setUserIdDeviceAffinity(IAudioPolicyCallback pcb, int userId, + @NonNull int[] deviceTypes, @NonNull String[] deviceAddresses) { + if (DEBUG_AP) { + Log.d(TAG, "setUserIdDeviceAffinity for " + pcb.asBinder() + " user:" + userId); + } + + synchronized (mAudioPolicies) { + final AudioPolicyProxy app = + checkUpdateForPolicy(pcb, "Cannot change device affinity in audio policy"); + if (app == null) { + return AudioManager.ERROR; + } + if (!app.hasMixRoutedToDevices(deviceTypes, deviceAddresses)) { + return AudioManager.ERROR; + } + return app.setUserIdDeviceAffinities(userId, deviceTypes, deviceAddresses); + } + } + /** see AudioPolicy.removeUidDeviceAffinity() */ public int removeUidDeviceAffinity(IAudioPolicyCallback pcb, int uid) { if (DEBUG_AP) { @@ -7003,6 +7076,22 @@ public class AudioService extends IAudioService.Stub } } + /** see AudioPolicy.removeUserIdDeviceAffinity() */ + public int removeUserIdDeviceAffinity(IAudioPolicyCallback pcb, int userId) { + if (DEBUG_AP) { + Log.d(TAG, "removeUserIdDeviceAffinity for " + pcb.asBinder() + + " userId:" + userId); + } + synchronized (mAudioPolicies) { + final AudioPolicyProxy app = + checkUpdateForPolicy(pcb, "Cannot remove device affinity in audio policy"); + if (app == null) { + return AudioManager.ERROR; + } + return app.removeUserIdDeviceAffinities(userId); + } + } + public int setFocusPropertiesForPolicy(int duckingBehavior, IAudioPolicyCallback pcb) { if (DEBUG_AP) Log.d(TAG, "setFocusPropertiesForPolicy() duck behavior=" + duckingBehavior + " policy " + pcb.asBinder()); @@ -7238,6 +7327,9 @@ public class AudioService extends IAudioService.Stub final HashMap<Integer, AudioDeviceArray> mUidDeviceAffinities = new HashMap<Integer, AudioDeviceArray>(); + final HashMap<Integer, AudioDeviceArray> mUserIdDeviceAffinities = + new HashMap<>(); + final IMediaProjection mProjection; private final class UnregisterOnStopCallback extends IMediaProjectionCallback.Stub { public void onStop() { @@ -7429,6 +7521,45 @@ public class AudioService extends IAudioService.Stub return AudioManager.ERROR; } + int setUserIdDeviceAffinities(int userId, + @NonNull int[] types, @NonNull String[] addresses) { + final Integer UserId = new Integer(userId); + int res; + if (mUserIdDeviceAffinities.remove(UserId) != null) { + final long identity = Binder.clearCallingIdentity(); + res = AudioSystem.removeUserIdDeviceAffinities(UserId); + Binder.restoreCallingIdentity(identity); + if (res != AudioSystem.SUCCESS) { + Log.e(TAG, "AudioSystem. removeUserIdDeviceAffinities(" + + UserId + ") failed, " + + " cannot call AudioSystem.setUserIdDeviceAffinities"); + return AudioManager.ERROR; + } + } + final long identity = Binder.clearCallingIdentity(); + res = AudioSystem.setUserIdDeviceAffinities(userId, types, addresses); + Binder.restoreCallingIdentity(identity); + if (res == AudioSystem.SUCCESS) { + mUserIdDeviceAffinities.put(UserId, new AudioDeviceArray(types, addresses)); + return AudioManager.SUCCESS; + } + Log.e(TAG, "AudioSystem.setUserIdDeviceAffinities(" + userId + ") failed"); + return AudioManager.ERROR; + } + + int removeUserIdDeviceAffinities(int userId) { + if (mUserIdDeviceAffinities.remove(new Integer(userId)) != null) { + final long identity = Binder.clearCallingIdentity(); + final int res = AudioSystem.removeUserIdDeviceAffinities(userId); + Binder.restoreCallingIdentity(identity); + if (res == AudioSystem.SUCCESS) { + return AudioManager.SUCCESS; + } + } + Log.e(TAG, "AudioSystem.removeUserIdDeviceAffinities failed"); + return AudioManager.ERROR; + } + /** @return human readable debug informations summarizing the state of the object. */ public String toLogFriendlyString() { String textDump = super.toLogFriendlyString(); diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java index a0c8e2325f77..a0573db680b4 100644 --- a/services/core/java/com/android/server/biometrics/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/face/FaceService.java @@ -118,14 +118,16 @@ public class FaceService extends BiometricServiceBase { private int mError; // Only valid if mError is ERROR_VENDOR private int mVendorError; + private int mUser; AuthenticationEvent(long startTime, long latency, boolean authenticated, int error, - int vendorError) { + int vendorError, int user) { mStartTime = startTime; mLatency = latency; mAuthenticated = authenticated; mError = error; mVendorError = vendorError; + mUser = user; } public String toString(Context context) { @@ -134,6 +136,7 @@ public class FaceService extends BiometricServiceBase { + "\tAuthenticated: " + mAuthenticated + "\tError: " + mError + "\tVendorCode: " + mVendorError + + "\tUser: " + mUser + "\t" + FaceManager.getErrorString(context, mError, mVendorError); } } @@ -242,7 +245,8 @@ public class FaceService extends BiometricServiceBase { System.currentTimeMillis() - getStartTimeMs() /* latency */, authenticated, 0 /* error */, - 0 /* vendorError */)); + 0 /* vendorError */, + getTargetUserId())); // For face, the authentication lifecycle ends either when // 1) Authenticated == true @@ -260,7 +264,8 @@ public class FaceService extends BiometricServiceBase { System.currentTimeMillis() - getStartTimeMs() /* latency */, false /* authenticated */, error, - vendorCode)); + vendorCode, + getTargetUserId())); return super.onError(deviceId, error, vendorCode); } diff --git a/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java b/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java index 299cb66faf0c..4fb6607a6b08 100644 --- a/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java +++ b/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java @@ -82,12 +82,14 @@ public class DefaultNetworkMetrics { } /** - * Convert events in the ring buffer to protos and add to the given list + * Convert events in the ring buffer to a list of IpConnectivityEvent protos */ - public synchronized void listEventsAsProto(List<IpConnectivityEvent> out) { + public synchronized List<IpConnectivityEvent> listEventsAsProto() { + List<IpConnectivityEvent> list = new ArrayList<>(); for (DefaultNetworkEvent ev : mEventsLog.toArray()) { - out.add(IpConnectivityEventBuilder.toProto(ev)); + list.add(IpConnectivityEventBuilder.toProto(ev)); } + return list; } public synchronized void flushEvents(List<IpConnectivityEvent> out) { diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java index 1337a93476bc..2c06d8230f13 100644 --- a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java +++ b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java @@ -244,9 +244,9 @@ final public class IpConnectivityMetrics extends SystemService { private List<IpConnectivityEvent> listEventsAsProtos() { final List<IpConnectivityEvent> events = IpConnectivityEventBuilder.toProto(getEvents()); if (mNetdListener != null) { - mNetdListener.listAsProtos(events); + events.addAll(mNetdListener.listAsProtos()); } - mDefaultNetworkMetrics.listEventsAsProto(events); + events.addAll(mDefaultNetworkMetrics.listEventsAsProto()); return events; } diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java index 0d3105139b88..f2892cc81951 100644 --- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java +++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java @@ -367,18 +367,20 @@ public class NetdEventListenerService extends INetdEventListener.Stub { } /** - * Convert events in the buffer to protos and add to the given list + * Convert events in the buffer to a list of IpConnectivityEvent protos */ - public synchronized void listAsProtos(List<IpConnectivityEvent> out) { + public synchronized List<IpConnectivityEvent> listAsProtos() { + List<IpConnectivityEvent> list = new ArrayList<>(); for (int i = 0; i < mNetworkMetrics.size(); i++) { - out.add(IpConnectivityEventBuilder.toProto(mNetworkMetrics.valueAt(i).connectMetrics)); + list.add(IpConnectivityEventBuilder.toProto(mNetworkMetrics.valueAt(i).connectMetrics)); } for (int i = 0; i < mNetworkMetrics.size(); i++) { - out.add(IpConnectivityEventBuilder.toProto(mNetworkMetrics.valueAt(i).dnsMetrics)); + list.add(IpConnectivityEventBuilder.toProto(mNetworkMetrics.valueAt(i).dnsMetrics)); } for (int i = 0; i < mWakeupStats.size(); i++) { - out.add(IpConnectivityEventBuilder.toProto(mWakeupStats.valueAt(i))); + list.add(IpConnectivityEventBuilder.toProto(mWakeupStats.valueAt(i))); } + return list; } private long getTransports(int netId) { diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java index 28f67fe2d618..aa39926d2310 100644 --- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java +++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java @@ -109,11 +109,17 @@ public abstract class BrightnessMappingStrategy { return levels; } - private static float[] getFloatArray(TypedArray array) { + /** + * Extracts a float array from the specified {@link TypedArray}. + * + * @param array The array to convert. + * @return the given array as a float array. + */ + public static float[] getFloatArray(TypedArray array) { final int N = array.length(); float[] vals = new float[N]; for (int i = 0; i < N; i++) { - vals[i] = array.getFloat(i, -1.0f); + vals[i] = array.getFloat(i, PowerManager.BRIGHTNESS_OFF_FLOAT); } array.recycle(); return vals; @@ -335,7 +341,7 @@ public abstract class BrightnessMappingStrategy { } } - protected float normalizeAbsoluteBrightness(int brightness) { + protected static float normalizeAbsoluteBrightness(int brightness) { brightness = MathUtils.constrain(brightness, PowerManager.BRIGHTNESS_OFF, PowerManager.BRIGHTNESS_ON); return (float) brightness / PowerManager.BRIGHTNESS_ON; diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java new file mode 100644 index 000000000000..e09cf6178981 --- /dev/null +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -0,0 +1,157 @@ +/* + * 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.display; + +import android.os.Environment; +import android.util.Slog; + +import com.android.server.display.config.DisplayConfiguration; +import com.android.server.display.config.NitsMap; +import com.android.server.display.config.Point; +import com.android.server.display.config.XmlParser; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; +import java.util.List; + +import javax.xml.datatype.DatatypeConfigurationException; + +/** + * Reads and stores display-specific configurations. + */ +public class DisplayDeviceConfig { + private static final String TAG = "DisplayDeviceConfig"; + + public static final float HIGH_BRIGHTNESS_MODE_UNSUPPORTED = Float.NaN; + + private static final String ETC_DIR = "etc"; + private static final String DISPLAY_CONFIG_DIR = "displayconfig"; + private static final String CONFIG_FILE_FORMAT = "display_%d.xml"; + + private float[] mNits; + private float[] mBrightness; + private BigDecimal mHighBrightnessModeStart; + + private DisplayDeviceConfig() { + } + + /** + * Creates an instance for the specified display. + * + * @param physicalDisplayId The display ID for which to load the configuration. + * @return A configuration instance for the specified display. + */ + public static DisplayDeviceConfig create(long physicalDisplayId) { + final DisplayDeviceConfig config = new DisplayDeviceConfig(); + final String filename = String.format(CONFIG_FILE_FORMAT, physicalDisplayId); + + config.initFromFile(Environment.buildPath( + Environment.getProductDirectory(), ETC_DIR, DISPLAY_CONFIG_DIR, filename)); + return config; + } + + /** + * Return the brightness mapping nits array if one is defined in the configuration file. + * + * @return The brightness mapping nits array. + */ + public float[] getNits() { + return mNits; + } + + /** + * Return the brightness mapping value array if one is defined in the configuration file. + * + * @return The brightness mapping value array. + */ + public float[] getBrightness() { + return mBrightness; + } + + /** + * Returns the point along the brightness value range {@link #getBrightness()} that + * high-brightness-mode begins. If high-brightness-mode is not supported, then + * Float.NaN is returned. + * + * @return The high brightness mode threshold, or Float.NaN if not supported. + */ + public float getHighBrightnessModeStart() { + return mHighBrightnessModeStart != null + ? mHighBrightnessModeStart.floatValue() : HIGH_BRIGHTNESS_MODE_UNSUPPORTED; + } + + private void initFromFile(File configFile) { + if (!configFile.exists()) { + // Display configuration files aren't required to exist. + return; + } + + if (!configFile.isFile()) { + Slog.e(TAG, "Display configuration is not a file: " + configFile + ", skipping"); + return; + } + + try (InputStream in = new BufferedInputStream(new FileInputStream(configFile))) { + final DisplayConfiguration config = XmlParser.read(in); + loadBrightnessMap(config); + } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) { + Slog.e(TAG, "Encountered an error while reading/parsing display config file: " + + configFile, e); + } + } + + private void loadBrightnessMap(DisplayConfiguration config) { + final NitsMap map = config.getScreenBrightnessMap(); + final List<Point> points = map.getPoint(); + final int size = points.size(); + + float[] nits = new float[size]; + float[] backlight = new float[size]; + + int i = 0; + for (Point point : points) { + nits[i] = point.getNits().floatValue(); + backlight[i] = point.getValue().floatValue(); + if (i > 0) { + if (nits[i] < nits[i - 1]) { + Slog.e(TAG, "screenBrightnessMap must be non-decreasing, ignoring rest " + + " of configuration. Nits: " + nits[i] + " < " + nits[i - 1]); + return; + } + + if (backlight[i] < backlight[i - 1]) { + Slog.e(TAG, "screenBrightnessMap must be non-decreasing, ignoring rest " + + " of configuration. Value: " + backlight[i] + " < " + + backlight[i - 1]); + return; + } + } + ++i; + } + final BigDecimal hbmStart = map.getHighBrightnessStart(); + + mHighBrightnessModeStart = hbmStart; + mNits = nits; + mBrightness = backlight; + } +} diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index fb8a4193286b..1f17f9f1ca43 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -30,6 +30,7 @@ import android.os.Trace; import android.util.LongSparseArray; import android.util.Slog; import android.util.SparseArray; +import android.util.Spline; import android.view.Display; import android.view.DisplayAddress; import android.view.DisplayCutout; @@ -37,6 +38,7 @@ import android.view.DisplayEventReceiver; import android.view.Surface; import android.view.SurfaceControl; +import com.android.internal.os.BackgroundThread; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.LocalServices; import com.android.server.lights.Light; @@ -187,8 +189,10 @@ final class LocalDisplayAdapter extends DisplayAdapter { private boolean mGameContentTypeRequested; private boolean mSidekickActive; private SidekickInternal mSidekickInternal; - private SurfaceControl.PhysicalDisplayInfo[] mDisplayInfos; + private Spline mSystemBrightnessToNits; + private Spline mNitsToHalBrightness; + private boolean mHalBrightnessSupport; LocalDisplayDevice(IBinder displayToken, long physicalDisplayId, SurfaceControl.PhysicalDisplayInfo[] physicalDisplayInfos, int activeDisplayInfo, @@ -210,6 +214,11 @@ final class LocalDisplayAdapter extends DisplayAdapter { mHdrCapabilities = SurfaceControl.getHdrCapabilities(displayToken); mAllmSupported = SurfaceControl.getAutoLowLatencyModeSupport(displayToken); mGameContentTypeSupported = SurfaceControl.getGameContentTypeSupport(displayToken); + mHalBrightnessSupport = SurfaceControl.getDisplayBrightnessSupport(displayToken); + + // Defer configuration file loading + BackgroundThread.getHandler().sendMessage(PooledLambda.obtainMessage( + LocalDisplayDevice::loadDisplayConfigurationBrightnessMapping, this)); } @Override @@ -338,6 +347,41 @@ final class LocalDisplayAdapter extends DisplayAdapter { return true; } + private void loadDisplayConfigurationBrightnessMapping() { + Spline nitsToHal = null; + Spline sysToNits = null; + + // Load the mapping from nits to HAL brightness range (display-device-config.xml) + DisplayDeviceConfig config = DisplayDeviceConfig.create(mPhysicalDisplayId); + if (config == null) { + return; + } + final float[] halNits = config.getNits(); + final float[] halBrightness = config.getBrightness(); + if (halNits == null || halBrightness == null) { + return; + } + nitsToHal = Spline.createSpline(halNits, halBrightness); + + // Load the mapping from system brightness range to nits (config.xml) + final Resources res = getOverlayContext().getResources(); + final float[] sysNits = BrightnessMappingStrategy.getFloatArray(res.obtainTypedArray( + com.android.internal.R.array.config_screenBrightnessNits)); + final int[] sysBrightness = res.getIntArray( + com.android.internal.R.array.config_screenBrightnessBacklight); + if (sysNits.length == 0 || sysBrightness.length != sysNits.length) { + return; + } + final float[] sysBrightnessFloat = new float[sysBrightness.length]; + for (int i = 0; i < sysBrightness.length; i++) { + sysBrightnessFloat[i] = sysBrightness[i]; + } + sysToNits = Spline.createSpline(sysBrightnessFloat, sysNits); + + mNitsToHalBrightness = nitsToHal; + mSystemBrightnessToNits = sysToNits; + } + private boolean updateColorModesLocked(int[] colorModes, int activeColorMode) { List<Integer> pendingColorModes = new ArrayList<>(); @@ -628,13 +672,37 @@ final class LocalDisplayAdapter extends DisplayAdapter { Trace.traceBegin(Trace.TRACE_TAG_POWER, "setDisplayBrightness(" + "id=" + physicalDisplayId + ", brightness=" + brightness + ")"); try { - mBacklight.setBrightness(brightness); + if (mHalBrightnessSupport) { + mBacklight.setBrightnessFloat( + displayBrightnessToHalBrightness(brightness)); + } else { + mBacklight.setBrightness(brightness); + } Trace.traceCounter(Trace.TRACE_TAG_POWER, "ScreenBrightness", brightness); } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } } + + /** + * Converts brightness range from the framework's brightness space to the + * Hal brightness space if the HAL brightness space has been provided via + * a display device configuration file. + */ + private float displayBrightnessToHalBrightness(int brightness) { + if (mSystemBrightnessToNits == null || mNitsToHalBrightness == null) { + return PowerManager.BRIGHTNESS_INVALID_FLOAT; + } + + if (brightness == 0) { + return PowerManager.BRIGHTNESS_OFF_FLOAT; + } + + final float nits = mSystemBrightnessToNits.interpolate(brightness); + final float halBrightness = mNitsToHalBrightness.interpolate(nits); + return halBrightness; + } }; } return null; diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java index b1639a948ffc..2926ec94417f 100644 --- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java +++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java @@ -67,7 +67,9 @@ import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; /** Implementation of {@link AppIntegrityManagerService}. */ @@ -221,6 +223,15 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { } String installerPackageName = getInstallerPackageName(intent); + + // Skip integrity verification if the verifier is doing the install. + if (isRuleProvider(installerPackageName)) { + Slog.i(TAG, "Verifier doing the install. Skipping integrity check."); + mPackageManagerInternal.setIntegrityVerificationResult( + verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW); + return; + } + String appCert = getCertificateFingerprint(packageInfo); AppInstallMetadata.Builder builder = new AppInstallMetadata.Builder(); @@ -271,7 +282,7 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { * Verify the UID and return the installer package name. * * @return the package name of the installer, or null if it cannot be determined or it is - * installed via adb. + * installed via adb. */ @Nullable private String getInstallerPackageName(Intent intent) { @@ -518,9 +529,17 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { } private String getCallerPackageNameOrThrow() { - final String[] allowedRuleProviders = - mContext.getResources() - .getStringArray(R.array.config_integrityRuleProviderPackages); + String callerPackageName = getCallerPackageName(); + if (callerPackageName == null) { + throw new SecurityException( + "Only system packages specified in config_integrityRuleProviderPackages are" + + " allowed to call this method."); + } + return callerPackageName; + } + + private String getCallerPackageName() { + final List<String> allowedRuleProviders = getAllowedRuleProviders(); for (String packageName : allowedRuleProviders) { try { // At least in tests, getPackageUid gives "NameNotFound" but getPackagesFromUid @@ -537,9 +556,7 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { Slog.i(TAG, "Rule provider package " + packageName + " not installed."); } } - throw new SecurityException( - "Only system packages specified in config_integrityRuleProviderPackages are" - + " allowed to call this method."); + return null; } private boolean isSystemApp(String packageName) { @@ -552,4 +569,14 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { return false; } } + + private List<String> getAllowedRuleProviders() { + return Arrays.asList(mContext.getResources().getStringArray( + R.array.config_integrityRuleProviderPackages)); + } + + private boolean isRuleProvider(String installerPackageName) { + return getAllowedRuleProviders().stream().anyMatch( + ruleProvider -> ruleProvider.equals(installerPackageName)); + } } diff --git a/services/core/java/com/android/server/lights/Light.java b/services/core/java/com/android/server/lights/Light.java index 717e3dae479d..998c7c66b504 100644 --- a/services/core/java/com/android/server/lights/Light.java +++ b/services/core/java/com/android/server/lights/Light.java @@ -49,6 +49,12 @@ public abstract class Light { */ public abstract void setBrightness(int brightness, int brightnessMode); + /** + * Set the brightness of a display using the brightness range defines in a + * display-device-configuration file. + */ + public abstract void setBrightnessFloat(float brightness); + public abstract void setColor(int color); public abstract void setFlashing(int color, int mode, int onMS, int offMS); public abstract void pulse(); diff --git a/services/core/java/com/android/server/lights/LightsService.java b/services/core/java/com/android/server/lights/LightsService.java index eaae2ed40b2e..8e6e1d6502de 100644 --- a/services/core/java/com/android/server/lights/LightsService.java +++ b/services/core/java/com/android/server/lights/LightsService.java @@ -58,12 +58,23 @@ public class LightsService extends SystemService { } @Override + public void setBrightnessFloat(float brightness) { + if (!Float.isNaN(brightness)) { + setBrightness(brightness, 0, BRIGHTNESS_MODE_USER); + } + } + + @Override public void setBrightness(int brightness) { setBrightness(brightness, BRIGHTNESS_MODE_USER); } @Override public void setBrightness(int brightness, int brightnessMode) { + setBrightness(Float.NaN, brightness, brightnessMode); + } + + private void setBrightness(float brightnessFloat, int brightness, int brightnessMode) { synchronized (this) { // LOW_PERSISTENCE cannot be manually set if (brightnessMode == BRIGHTNESS_MODE_LOW_PERSISTENCE) { @@ -84,7 +95,10 @@ public class LightsService extends SystemService { if (DEBUG) { Slog.d(TAG, "Using new setBrightness path!"); } - if (brightness == 0) { + + if (!Float.isNaN(brightnessFloat)) { + SurfaceControl.setDisplayBrightness(mDisplayToken, brightnessFloat); + } else if (brightness == 0) { SurfaceControl.setDisplayBrightness(mDisplayToken, -1.0f); } else { SurfaceControl.setDisplayBrightness(mDisplayToken, diff --git a/services/core/java/com/android/server/lights/OWNERS b/services/core/java/com/android/server/lights/OWNERS index c7c6d5658d1d..0e795b9a5ef8 100644 --- a/services/core/java/com/android/server/lights/OWNERS +++ b/services/core/java/com/android/server/lights/OWNERS @@ -1,2 +1,3 @@ michaelwr@google.com -dangittik@google.com +santoscordon@google.com +flc@google.com diff --git a/services/core/java/com/android/server/location/ActivityRecognitionProxy.java b/services/core/java/com/android/server/location/ActivityRecognitionProxy.java deleted file mode 100644 index 80ab7903cef7..000000000000 --- a/services/core/java/com/android/server/location/ActivityRecognitionProxy.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.location; - -import android.content.Context; -import android.hardware.location.ActivityRecognitionHardware; -import android.hardware.location.IActivityRecognitionHardwareClient; -import android.hardware.location.IActivityRecognitionHardwareWatcher; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import com.android.server.FgThread; -import com.android.server.ServiceWatcher; - -/** - * Proxy class to bind GmsCore to the ActivityRecognitionHardware. - * - * @hide - */ -public class ActivityRecognitionProxy { - - private static final String TAG = "ActivityRecognitionProxy"; - - /** - * Creates an instance of the proxy and binds it to the appropriate FusedProvider. - * - * @return An instance of the proxy if it could be bound, null otherwise. - */ - public static ActivityRecognitionProxy createAndBind( - Context context, - boolean activityRecognitionHardwareIsSupported, - ActivityRecognitionHardware activityRecognitionHardware, - int overlaySwitchResId, - int defaultServicePackageNameResId, - int initialPackageNameResId) { - ActivityRecognitionProxy activityRecognitionProxy = new ActivityRecognitionProxy( - context, - activityRecognitionHardwareIsSupported, - activityRecognitionHardware, - overlaySwitchResId, - defaultServicePackageNameResId, - initialPackageNameResId); - - if (activityRecognitionProxy.mServiceWatcher.start()) { - return activityRecognitionProxy; - } else { - return null; - } - } - - private final ServiceWatcher mServiceWatcher; - private final boolean mIsSupported; - private final ActivityRecognitionHardware mInstance; - - private ActivityRecognitionProxy( - Context context, - boolean activityRecognitionHardwareIsSupported, - ActivityRecognitionHardware activityRecognitionHardware, - int overlaySwitchResId, - int defaultServicePackageNameResId, - int initialPackageNameResId) { - mIsSupported = activityRecognitionHardwareIsSupported; - mInstance = activityRecognitionHardware; - - mServiceWatcher = new ServiceWatcher( - context, - TAG, - "com.android.location.service.ActivityRecognitionProvider", - overlaySwitchResId, - defaultServicePackageNameResId, - initialPackageNameResId, - FgThread.getHandler()) { - @Override - protected void onBind() { - runOnBinder(ActivityRecognitionProxy.this::initializeService); - } - }; - } - - private void initializeService(IBinder binder) { - try { - String descriptor = binder.getInterfaceDescriptor(); - - if (IActivityRecognitionHardwareWatcher.class.getCanonicalName().equals( - descriptor)) { - IActivityRecognitionHardwareWatcher watcher = - IActivityRecognitionHardwareWatcher.Stub.asInterface(binder); - if (mInstance != null) { - watcher.onInstanceChanged(mInstance); - } - } else if (IActivityRecognitionHardwareClient.class.getCanonicalName() - .equals(descriptor)) { - IActivityRecognitionHardwareClient client = - IActivityRecognitionHardwareClient.Stub.asInterface(binder); - client.onAvailabilityChanged(mIsSupported, mInstance); - } else { - Log.e(TAG, "Invalid descriptor found on connection: " + descriptor); - } - } catch (RemoteException e) { - Log.w(TAG, e); - } - } -} diff --git a/services/core/java/com/android/server/location/GeocoderProxy.java b/services/core/java/com/android/server/location/GeocoderProxy.java index e6f0ed9d14b0..536f95a40431 100644 --- a/services/core/java/com/android/server/location/GeocoderProxy.java +++ b/services/core/java/com/android/server/location/GeocoderProxy.java @@ -16,6 +16,7 @@ package com.android.server.location; +import android.annotation.Nullable; import android.content.Context; import android.location.Address; import android.location.GeocoderParams; @@ -28,40 +29,38 @@ import java.util.List; /** * Proxy for IGeocodeProvider implementations. + * + * @hide */ public class GeocoderProxy { - private static final String TAG = "GeocoderProxy"; private static final String SERVICE_ACTION = "com.android.location.service.GeocodeProvider"; - private final ServiceWatcher mServiceWatcher; - - public static GeocoderProxy createAndBind(Context context, - int overlaySwitchResId, int defaultServicePackageNameResId, - int initialPackageNamesResId) { - GeocoderProxy proxy = new GeocoderProxy(context, overlaySwitchResId, - defaultServicePackageNameResId, initialPackageNamesResId); - if (proxy.bind()) { + /** + * Creates and registers this proxy. If no suitable service is available for the proxy, returns + * null. + */ + @Nullable + public static GeocoderProxy createAndRegister(Context context) { + GeocoderProxy proxy = new GeocoderProxy(context); + if (proxy.register()) { return proxy; } else { return null; } } - private GeocoderProxy(Context context, - int overlaySwitchResId, int defaultServicePackageNameResId, - int initialPackageNamesResId) { - mServiceWatcher = new ServiceWatcher(context, TAG, SERVICE_ACTION, overlaySwitchResId, - defaultServicePackageNameResId, initialPackageNamesResId, - BackgroundThread.getHandler()); - } + private final ServiceWatcher mServiceWatcher; - private boolean bind() { - return mServiceWatcher.start(); + private GeocoderProxy(Context context) { + mServiceWatcher = new ServiceWatcher(context, BackgroundThread.getHandler(), SERVICE_ACTION, + null, null, + com.android.internal.R.bool.config_enableGeocoderOverlay, + com.android.internal.R.string.config_geocoderProviderPackageName); } - public String getConnectedPackageName() { - return mServiceWatcher.getCurrentPackageName(); + private boolean register() { + return mServiceWatcher.register(); } public String getFromLocation(double latitude, double longitude, int maxResults, @@ -83,5 +82,4 @@ public class GeocoderProxy { maxResults, params, addrs); }, "Service not Available"); } - } diff --git a/services/core/java/com/android/server/location/GeofenceProxy.java b/services/core/java/com/android/server/location/GeofenceProxy.java index ce93661a8810..f006fb177382 100644 --- a/services/core/java/com/android/server/location/GeofenceProxy.java +++ b/services/core/java/com/android/server/location/GeofenceProxy.java @@ -22,7 +22,6 @@ import android.content.Intent; import android.content.ServiceConnection; import android.hardware.location.GeofenceHardwareService; import android.hardware.location.IGeofenceHardware; -import android.location.IFusedGeofenceHardware; import android.location.IGeofenceProvider; import android.location.IGpsGeofenceHardware; import android.os.IBinder; @@ -33,6 +32,8 @@ import android.util.Log; import com.android.server.FgThread; import com.android.server.ServiceWatcher; +import java.util.Objects; + /** * @hide */ @@ -41,64 +42,41 @@ public final class GeofenceProxy { private static final String TAG = "GeofenceProxy"; private static final String SERVICE_ACTION = "com.android.location.service.GeofenceProvider"; - private final Context mContext; - private final ServiceWatcher mServiceWatcher; - - @Nullable - private final IGpsGeofenceHardware mGpsGeofenceHardware; @Nullable - private final IFusedGeofenceHardware mFusedGeofenceHardware; - - private volatile IGeofenceHardware mGeofenceHardware; - - private final ServiceWatcher.BinderRunner mUpdateGeofenceHardware = (binder) -> { - IGeofenceProvider provider = IGeofenceProvider.Stub.asInterface(binder); - try { - provider.setGeofenceHardware(mGeofenceHardware); - } catch (RemoteException e) { - Log.w(TAG, e); - } - }; - - public static GeofenceProxy createAndBind(Context context, - int overlaySwitchResId, int defaultServicePackageNameResId, - int initialPackageNamesResId, @Nullable IGpsGeofenceHardware gpsGeofence, - @Nullable IFusedGeofenceHardware fusedGeofenceHardware) { - GeofenceProxy proxy = new GeofenceProxy(context, overlaySwitchResId, - defaultServicePackageNameResId, initialPackageNamesResId, gpsGeofence, - fusedGeofenceHardware); - - if (proxy.bind()) { + public static GeofenceProxy createAndBind(Context context, IGpsGeofenceHardware gpsGeofence) { + GeofenceProxy proxy = new GeofenceProxy(context, gpsGeofence); + if (proxy.register(context)) { return proxy; } else { return null; } } - private GeofenceProxy(Context context, - int overlaySwitchResId, int defaultServicePackageNameResId, - int initialPackageNamesResId, @Nullable IGpsGeofenceHardware gpsGeofence, - @Nullable IFusedGeofenceHardware fusedGeofenceHardware) { - mContext = context; - mServiceWatcher = new ServiceWatcher(context, TAG, SERVICE_ACTION, overlaySwitchResId, - defaultServicePackageNameResId, initialPackageNamesResId, - FgThread.getHandler()) { - @Override - protected void onBind() { - runOnBinder(mUpdateGeofenceHardware); - } - }; + private final IGpsGeofenceHardware mGpsGeofenceHardware; + private final ServiceWatcher mServiceWatcher; - mGpsGeofenceHardware = gpsGeofence; - mFusedGeofenceHardware = fusedGeofenceHardware; + private volatile IGeofenceHardware mGeofenceHardware; + + private GeofenceProxy(Context context, IGpsGeofenceHardware gpsGeofence) { + mGpsGeofenceHardware = Objects.requireNonNull(gpsGeofence); + mServiceWatcher = new ServiceWatcher(context, FgThread.getHandler(), SERVICE_ACTION, + this::updateGeofenceHardware, null, + com.android.internal.R.bool.config_enableGeofenceOverlay, + com.android.internal.R.string.config_geofenceProviderPackageName); mGeofenceHardware = null; } - private boolean bind() { - if (mServiceWatcher.start()) { - mContext.bindServiceAsUser(new Intent(mContext, GeofenceHardwareService.class), - new GeofenceProxyServiceConnection(), Context.BIND_AUTO_CREATE, + private void updateGeofenceHardware(IBinder binder) throws RemoteException { + IGeofenceProvider.Stub.asInterface(binder).setGeofenceHardware(mGeofenceHardware); + } + + private boolean register(Context context) { + if (mServiceWatcher.register()) { + context.bindServiceAsUser( + new Intent(context, GeofenceHardwareService.class), + new GeofenceProxyServiceConnection(), + Context.BIND_AUTO_CREATE, UserHandle.SYSTEM); return true; } @@ -113,24 +91,18 @@ public final class GeofenceProxy { IGeofenceHardware geofenceHardware = IGeofenceHardware.Stub.asInterface(service); try { - if (mGpsGeofenceHardware != null) { - geofenceHardware.setGpsGeofenceHardware(mGpsGeofenceHardware); - } - if (mFusedGeofenceHardware != null) { - geofenceHardware.setFusedGeofenceHardware(mFusedGeofenceHardware); - } - + geofenceHardware.setGpsGeofenceHardware(mGpsGeofenceHardware); mGeofenceHardware = geofenceHardware; - mServiceWatcher.runOnBinder(mUpdateGeofenceHardware); - } catch (Exception e) { - Log.w(TAG, e); + mServiceWatcher.runOnBinder(GeofenceProxy.this::updateGeofenceHardware); + } catch (RemoteException e) { + Log.w(TAG, "unable to initialize geofence hardware", e); } } @Override public void onServiceDisconnected(ComponentName name) { mGeofenceHardware = null; - mServiceWatcher.runOnBinder(mUpdateGeofenceHardware); + mServiceWatcher.runOnBinder(GeofenceProxy.this::updateGeofenceHardware); } } } diff --git a/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java b/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java new file mode 100644 index 000000000000..9d9852ba5da3 --- /dev/null +++ b/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.location; + +import android.annotation.Nullable; +import android.content.Context; +import android.hardware.location.ActivityRecognitionHardware; +import android.hardware.location.IActivityRecognitionHardwareClient; +import android.hardware.location.IActivityRecognitionHardwareWatcher; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +import com.android.server.FgThread; +import com.android.server.ServiceWatcher; + +/** + * Proxy class to bind GmsCore to the ActivityRecognitionHardware. + * + * @hide + */ +public class HardwareActivityRecognitionProxy { + + private static final String TAG = "ARProxy"; + private static final String SERVICE_ACTION = + "com.android.location.service.ActivityRecognitionProvider"; + + /** + * Creates and registers this proxy. If no suitable service is available for the proxy, returns + * null. + */ + @Nullable + public static HardwareActivityRecognitionProxy createAndRegister(Context context) { + HardwareActivityRecognitionProxy arProxy = new HardwareActivityRecognitionProxy(context); + if (arProxy.register()) { + return arProxy; + } else { + return null; + } + } + + private final boolean mIsSupported; + private final ActivityRecognitionHardware mInstance; + + private final ServiceWatcher mServiceWatcher; + + private HardwareActivityRecognitionProxy(Context context) { + mIsSupported = ActivityRecognitionHardware.isSupported(); + if (mIsSupported) { + mInstance = ActivityRecognitionHardware.getInstance(context); + } else { + mInstance = null; + } + + mServiceWatcher = new ServiceWatcher(context, + FgThread.getHandler(), + SERVICE_ACTION, + this::onBind, + null, + com.android.internal.R.bool.config_enableActivityRecognitionHardwareOverlay, + com.android.internal.R.string.config_activityRecognitionHardwarePackageName); + } + + private boolean register() { + return mServiceWatcher.register(); + } + + private void onBind(IBinder binder) throws RemoteException { + String descriptor = binder.getInterfaceDescriptor(); + + if (IActivityRecognitionHardwareWatcher.class.getCanonicalName().equals(descriptor)) { + IActivityRecognitionHardwareWatcher watcher = + IActivityRecognitionHardwareWatcher.Stub.asInterface(binder); + if (mInstance != null) { + watcher.onInstanceChanged(mInstance); + } + } else if (IActivityRecognitionHardwareClient.class.getCanonicalName().equals(descriptor)) { + IActivityRecognitionHardwareClient client = + IActivityRecognitionHardwareClient.Stub.asInterface(binder); + client.onAvailabilityChanged(mIsSupported, mInstance); + } else { + Log.e(TAG, "Unknown descriptor: " + descriptor); + } + } +} diff --git a/services/core/java/com/android/server/location/LocationProviderProxy.java b/services/core/java/com/android/server/location/LocationProviderProxy.java index 8a149afa6238..805b018a8f45 100644 --- a/services/core/java/com/android/server/location/LocationProviderProxy.java +++ b/services/core/java/com/android/server/location/LocationProviderProxy.java @@ -19,12 +19,11 @@ package com.android.server.location; import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; import android.annotation.Nullable; +import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.location.Location; import android.os.Bundle; -import android.os.Handler; -import android.os.HandlerExecutor; import android.os.IBinder; import android.os.RemoteException; import android.util.ArraySet; @@ -35,7 +34,6 @@ import com.android.internal.location.ILocationProviderManager; import com.android.internal.location.ProviderProperties; import com.android.internal.location.ProviderRequest; import com.android.server.FgThread; -import com.android.server.LocationManagerService; import com.android.server.ServiceWatcher; import java.io.FileDescriptor; @@ -49,17 +47,31 @@ import java.util.List; public class LocationProviderProxy extends AbstractLocationProvider { private static final String TAG = "LocationProviderProxy"; - private static final boolean D = LocationManagerService.D; private static final int MAX_ADDITIONAL_PACKAGES = 2; + /** + * Creates and registers this proxy. If no suitable service is available for the proxy, returns + * null. + */ + @Nullable + public static LocationProviderProxy createAndRegister(Context context, String action, + int enableOverlayResId, int nonOverlayPackageResId) { + LocationProviderProxy proxy = new LocationProviderProxy(context, action, enableOverlayResId, + nonOverlayPackageResId); + if (proxy.register()) { + return proxy; + } else { + return null; + } + } + private final ILocationProviderManager.Stub mManager = new ILocationProviderManager.Stub() { // executed on binder thread @Override public void onSetAdditionalProviderPackages(List<String> packageNames) { - int maxCount = Math.min(MAX_ADDITIONAL_PACKAGES, packageNames.size()) + 1; + int maxCount = Math.min(MAX_ADDITIONAL_PACKAGES, packageNames.size()); ArraySet<String> allPackages = new ArraySet<>(maxCount); - allPackages.add(mServiceWatcher.getCurrentPackageName()); for (String packageName : packageNames) { if (packageNames.size() >= maxCount) { return; @@ -74,6 +86,12 @@ public class LocationProviderProxy extends AbstractLocationProvider { } } + // add the binder package + ComponentName service = mServiceWatcher.getBoundService().component; + if (service != null) { + allPackages.add(service.getPackageName()); + } + setPackageNames(allPackages); } @@ -100,63 +118,39 @@ public class LocationProviderProxy extends AbstractLocationProvider { @Nullable private ProviderRequest mRequest; - /** - * Creates a new LocationProviderProxy and immediately begins binding to the best applicable - * service. - */ - @Nullable - public static LocationProviderProxy createAndBind(Context context, String action, - int overlaySwitchResId, int defaultServicePackageNameResId, - int initialPackageNamesResId) { - LocationProviderProxy proxy = new LocationProviderProxy(context, FgThread.getHandler(), - action, overlaySwitchResId, defaultServicePackageNameResId, - initialPackageNamesResId); - if (proxy.bind()) { - return proxy; - } else { - return null; - } - } - - private LocationProviderProxy(Context context, Handler handler, String action, - int overlaySwitchResId, int defaultServicePackageNameResId, - int initialPackageNamesResId) { - super(context, new HandlerExecutor(handler), Collections.emptySet()); + private LocationProviderProxy(Context context, String action, int enableOverlayResId, + int nonOverlayPackageResId) { + super(context, FgThread.getExecutor()); - mServiceWatcher = new ServiceWatcher(context, TAG, action, overlaySwitchResId, - defaultServicePackageNameResId, initialPackageNamesResId, handler) { - - @Override - protected void onBind() { - runOnBinder(LocationProviderProxy.this::initializeService); - } - - @Override - protected void onUnbind() { - setState(State.EMPTY_STATE); - } - }; + mServiceWatcher = new ServiceWatcher(context, FgThread.getHandler(), action, this::onBind, + this::onUnbind, enableOverlayResId, nonOverlayPackageResId); mRequest = null; } - private boolean bind() { - return mServiceWatcher.start(); + private boolean register() { + return mServiceWatcher.register(); } - private void initializeService(IBinder binder) throws RemoteException { - ILocationProvider service = ILocationProvider.Stub.asInterface(binder); - if (D) Log.d(TAG, "applying state to connected service " + mServiceWatcher); + private void onBind(IBinder binder) throws RemoteException { + ILocationProvider provider = ILocationProvider.Stub.asInterface(binder); - setPackageNames(Collections.singleton(mServiceWatcher.getCurrentPackageName())); + ComponentName service = mServiceWatcher.getBoundService().component; + if (service != null) { + setPackageNames(Collections.singleton(service.getPackageName())); + } - service.setLocationProviderManager(mManager); + provider.setLocationProviderManager(mManager); if (mRequest != null) { - service.setRequest(mRequest, mRequest.workSource); + provider.setRequest(mRequest, mRequest.workSource); } } + private void onUnbind() { + setState(State.EMPTY_STATE); + } + @Override public void onSetRequest(ProviderRequest request) { mServiceWatcher.runOnBinder(binder -> { diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java index 837c489c0cac..8179c32ae2df 100644 --- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java +++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java @@ -29,12 +29,13 @@ import android.content.Intent; import android.content.IntentFilter; import android.media.MediaRoute2Info; import android.text.TextUtils; -import android.util.Log; +import android.util.Slog; import android.util.SparseBooleanArray; import com.android.internal.R; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -59,7 +60,6 @@ class BluetoothRouteProvider { private final BroadcastReceiver mBroadcastReceiver = new BluetoothBroadcastReceiver(); private final BluetoothProfileListener mProfileListener = new BluetoothProfileListener(); - // TODO: The mActiveDevice should be set when BluetoothRouteProvider is created. private BluetoothDevice mActiveDevice = null; static synchronized BluetoothRouteProvider getInstance(@NonNull Context context, @@ -104,6 +104,43 @@ class BluetoothRouteProvider { mContext.registerReceiver(mBroadcastReceiver, mIntentFilter, null, null); } + /** + * Clears the active device for all known profiles. + */ + public void clearActiveDevices() { + BluetoothA2dp a2dpProfile = mA2dpProfile; + BluetoothHearingAid hearingAidProfile = mHearingAidProfile; + if (a2dpProfile != null) { + a2dpProfile.setActiveDevice(null); + } + if (hearingAidProfile != null) { + hearingAidProfile.setActiveDevice(null); + } + } + + /** + * Sets the active device. + * @param deviceId the id of the Bluetooth device + */ + public void setActiveDevice(@NonNull String deviceId) { + BluetoothRouteInfo btRouteInfo = mBluetoothRoutes.get(deviceId); + if (btRouteInfo == null) { + Slog.w(TAG, "setActiveDevice: unknown device id=" + deviceId); + return; + } + BluetoothA2dp a2dpProfile = mA2dpProfile; + BluetoothHearingAid hearingAidProfile = mHearingAidProfile; + + if (a2dpProfile != null + && btRouteInfo.connectedProfiles.get(BluetoothProfile.A2DP, false)) { + a2dpProfile.setActiveDevice(btRouteInfo.btDevice); + } + if (hearingAidProfile != null + && btRouteInfo.connectedProfiles.get(BluetoothProfile.HEARING_AID, false)) { + hearingAidProfile.setActiveDevice(btRouteInfo.btDevice); + } + } + private void addEventReceiver(String action, BluetoothEventReceiver eventReceiver) { mEventReceiverMap.put(action, eventReceiver); mIntentFilter.addAction(action); @@ -157,12 +194,12 @@ class BluetoothRouteProvider { private void setRouteConnectionStateForDevice(BluetoothDevice device, @MediaRoute2Info.ConnectionState int state) { if (device == null) { - Log.w(TAG, "setRouteConnectionStateForDevice: device shouldn't be null"); + Slog.w(TAG, "setRouteConnectionStateForDevice: device shouldn't be null"); return; } BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress()); if (btRoute == null) { - Log.w(TAG, "setRouteConnectionStateForDevice: route shouldn't be null"); + Slog.w(TAG, "setRouteConnectionStateForDevice: route shouldn't be null"); return; } if (btRoute.route.getConnectionState() != state) { @@ -184,24 +221,36 @@ class BluetoothRouteProvider { // These callbacks run on the main thread. private final class BluetoothProfileListener implements BluetoothProfile.ServiceListener { public void onServiceConnected(int profile, BluetoothProfile proxy) { + List<BluetoothDevice> activeDevices; switch (profile) { case BluetoothProfile.A2DP: mA2dpProfile = (BluetoothA2dp) proxy; + // It may contain null. + activeDevices = Collections.singletonList(mA2dpProfile.getActiveDevice()); break; case BluetoothProfile.HEARING_AID: mHearingAidProfile = (BluetoothHearingAid) proxy; + activeDevices = mHearingAidProfile.getActiveDevices(); break; default: return; } + //TODO: Check a pair of HAP devices whether there exist two or more active devices. for (BluetoothDevice device : proxy.getConnectedDevices()) { BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress()); if (btRoute == null) { btRoute = createBluetoothRoute(device); mBluetoothRoutes.put(device.getAddress(), btRoute); } + if (activeDevices.contains(device)) { + mActiveDevice = device; + setRouteConnectionStateForDevice(device, + MediaRoute2Info.CONNECTION_STATE_CONNECTED); + } + btRoute.connectedProfiles.put(profile, true); } + notifyBluetoothRoutesUpdated(); } public void onServiceDisconnected(int profile) { diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index f9169ee69d41..9594659f23d0 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -760,17 +760,12 @@ class MediaRouter2ServiceImpl { Slog.w(TAG, "selectClientRouteLocked: Ignoring unknown manager."); return; } - //TODO: we shouldn't ignore selecting request for unknown clients. (RCN?) Client2Record clientRecord = managerRecord.mUserRecord.mHandler .findClientforSessionLocked(sessionId); - if (clientRecord == null) { - Slog.w(TAG, "selectClientRouteLocked: Ignoring unknown session."); - return; - } - clientRecord.mUserRecord.mHandler.sendMessage( + managerRecord.mUserRecord.mHandler.sendMessage( obtainMessage(UserHandler::selectRouteOnHandler, - clientRecord.mUserRecord.mHandler, + managerRecord.mUserRecord.mHandler, clientRecord, sessionId, route)); } @@ -783,17 +778,12 @@ class MediaRouter2ServiceImpl { Slog.w(TAG, "deselectClientRouteLocked: Ignoring unknown manager."); return; } - //TODO: we shouldn't ignore selecting request for unknown clients. (RCN?) Client2Record clientRecord = managerRecord.mUserRecord.mHandler .findClientforSessionLocked(sessionId); - if (clientRecord == null) { - Slog.w(TAG, "deslectClientRouteLocked: Ignoring unknown session."); - return; - } - clientRecord.mUserRecord.mHandler.sendMessage( + managerRecord.mUserRecord.mHandler.sendMessage( obtainMessage(UserHandler::deselectRouteOnHandler, - clientRecord.mUserRecord.mHandler, + managerRecord.mUserRecord.mHandler, clientRecord, sessionId, route)); } @@ -806,17 +796,12 @@ class MediaRouter2ServiceImpl { Slog.w(TAG, "transferClientRouteLocked: Ignoring unknown manager."); return; } - //TODO: we shouldn't ignore selecting request for unknown clients. (RCN?) Client2Record clientRecord = managerRecord.mUserRecord.mHandler .findClientforSessionLocked(sessionId); - if (clientRecord == null) { - Slog.w(TAG, "transferClientRouteLocked: Ignoring unknown session."); - return; - } - clientRecord.mUserRecord.mHandler.sendMessage( + managerRecord.mUserRecord.mHandler.sendMessage( obtainMessage(UserHandler::transferToRouteOnHandler, - clientRecord.mUserRecord.mHandler, + managerRecord.mUserRecord.mHandler, clientRecord, sessionId, route)); } @@ -1166,7 +1151,7 @@ class MediaRouter2ServiceImpl { requestId, sessionHints); } - private void selectRouteOnHandler(@NonNull Client2Record clientRecord, + private void selectRouteOnHandler(@Nullable Client2Record clientRecord, String uniqueSessionId, MediaRoute2Info route) { if (!checkArgumentsForSessionControl(clientRecord, uniqueSessionId, route, "selecting")) { @@ -1182,7 +1167,7 @@ class MediaRouter2ServiceImpl { provider.selectRoute(getOriginalId(uniqueSessionId), route.getOriginalId()); } - private void deselectRouteOnHandler(@NonNull Client2Record clientRecord, + private void deselectRouteOnHandler(@Nullable Client2Record clientRecord, String uniqueSessionId, MediaRoute2Info route) { if (!checkArgumentsForSessionControl(clientRecord, uniqueSessionId, route, "deselecting")) { @@ -1198,7 +1183,7 @@ class MediaRouter2ServiceImpl { provider.deselectRoute(getOriginalId(uniqueSessionId), route.getOriginalId()); } - private void transferToRouteOnHandler(@NonNull Client2Record clientRecord, + private void transferToRouteOnHandler(Client2Record clientRecord, String uniqueSessionId, MediaRoute2Info route) { if (!checkArgumentsForSessionControl(clientRecord, uniqueSessionId, route, "transferring to")) { @@ -1215,7 +1200,7 @@ class MediaRouter2ServiceImpl { route.getOriginalId()); } - private boolean checkArgumentsForSessionControl(@NonNull Client2Record clientRecord, + private boolean checkArgumentsForSessionControl(@Nullable Client2Record clientRecord, String uniqueSessionId, MediaRoute2Info route, @NonNull String description) { if (route == null) { Slog.w(TAG, "Ignoring " + description + " null route"); @@ -1236,6 +1221,17 @@ class MediaRouter2ServiceImpl { return false; } + // Bypass checking client if it's the system session (clientRecord should be null) + if (TextUtils.equals(getProviderId(uniqueSessionId), mSystemProvider.getUniqueId())) { + return true; + } + + //TODO: Handle RCN case. + if (clientRecord == null) { + Slog.w(TAG, "Ignoring " + description + " route from unknown client."); + return false; + } + Client2Record matchingRecord = mSessionToClientMap.get(uniqueSessionId); if (matchingRecord != clientRecord) { Slog.w(TAG, "Ignoring " + description + " route from non-matching client. " diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 4a6fcdf73b90..a6ad57a7ae3a 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -103,6 +103,7 @@ public class MediaSessionService extends SystemService implements Monitor { private static final int WAKELOCK_TIMEOUT = 5000; private static final int MEDIA_KEY_LISTENER_TIMEOUT = 1000; + private static final int SESSION_CREATION_LIMIT_PER_UID = 100; private final Context mContext; private final SessionManagerImpl mSessionManagerImpl; @@ -428,6 +429,18 @@ public class MediaSessionService extends SystemService implements Monitor { Log.d(TAG, "Destroying " + session); } FullUserRecord user = getFullUserRecordLocked(session.getUserId()); + + if (user != null) { + final int uid = session.getUid(); + final int sessionCount = user.mUidToSessionCount.get(uid, 0); + if (sessionCount <= 0) { + Log.w(TAG, "destroySessionLocked: sessionCount should be positive. " + + "sessionCount=" + sessionCount); + } else { + user.mUidToSessionCount.put(uid, sessionCount - 1); + } + } + if (mGlobalPrioritySession == session) { mGlobalPrioritySession = null; if (session.isActive() && user != null) { @@ -490,6 +503,20 @@ public class MediaSessionService extends SystemService implements Monitor { } } + private boolean hasMediaControlPermission(int pid, int uid) { + // Check if it's system server or has MEDIA_CONTENT_CONTROL. + // Note that system server doesn't have MEDIA_CONTENT_CONTROL, so we need extra + // check here. + if (uid == Process.SYSTEM_UID || mContext.checkPermission( + android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid) + == PackageManager.PERMISSION_GRANTED) { + return true; + } else if (DEBUG) { + Log.d(TAG, "uid(" + uid + ") hasn't granted MEDIA_CONTENT_CONTROL"); + } + return false; + } + /** * This checks if the component is an enabled notification listener for the * specified user. Enabled components may only operate on behalf of the user @@ -544,6 +571,14 @@ public class MediaSessionService extends SystemService implements Monitor { throw new RuntimeException("Media Session owner died prematurely.", e); } + final int sessionCount = user.mUidToSessionCount.get(callerUid, 0); + if (sessionCount >= SESSION_CREATION_LIMIT_PER_UID + && !hasMediaControlPermission(callerPid, callerUid)) { + throw new RuntimeException("Created too many sessions. count=" + + sessionCount + ")"); + } + user.mUidToSessionCount.put(callerUid, sessionCount + 1); + user.mPriorityStack.addSession(session); mHandler.postSessionsChanged(session); @@ -723,6 +758,7 @@ public class MediaSessionService extends SystemService implements Monitor { mOnMediaKeyEventDispatchedListeners = new HashMap<>(); private final HashMap<IBinder, OnMediaKeyEventSessionChangedListenerRecord> mOnMediaKeyEventSessionChangedListeners = new HashMap<>(); + private final SparseIntArray mUidToSessionCount = new SparseIntArray(); private PendingIntent mLastMediaButtonReceiver; private ComponentName mRestoredMediaButtonReceiver; @@ -1954,20 +1990,6 @@ public class MediaSessionService extends SystemService implements Monitor { return resolvedUserId; } - private boolean hasMediaControlPermission(int pid, int uid) { - // Check if it's system server or has MEDIA_CONTENT_CONTROL. - // Note that system server doesn't have MEDIA_CONTENT_CONTROL, so we need extra - // check here. - if (uid == Process.SYSTEM_UID || mContext.checkPermission( - android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid) - == PackageManager.PERMISSION_GRANTED) { - return true; - } else if (DEBUG) { - Log.d(TAG, "uid(" + uid + ") hasn't granted MEDIA_CONTENT_CONTROL"); - } - return false; - } - private boolean hasEnabledNotificationListener(int resolvedUserId, String packageName) throws RemoteException { // You may not access another user's content as an enabled listener. diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 6f6d8a1e8df4..558eb8d05783 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -64,7 +64,6 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { SystemMediaRoute2Provider.class.getPackageName$(), SystemMediaRoute2Provider.class.getName()); - //TODO: Clean up these when audio manager support multiple bt devices MediaRoute2Info mDefaultRoute; @NonNull List<MediaRoute2Info> mBluetoothRoutes = Collections.EMPTY_LIST; final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo(); @@ -91,6 +90,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { mAudioService = IAudioService.Stub.asInterface( ServiceManager.getService(Context.AUDIO_SERVICE)); + initializeDefaultRoute(); mBtRouteProvider = BluetoothRouteProvider.getInstance(context, (routes) -> { mBluetoothRoutes = routes; publishRoutes(); @@ -103,7 +103,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { notifySessionInfoUpdated(); } }); - initializeRoutes(); + initializeSessionInfo(); } @Override @@ -119,17 +119,21 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { @Override public void selectRoute(String sessionId, String routeId) { - //TODO: implement method + // Do nothing since we don't support multiple BT yet. } @Override public void deselectRoute(String sessionId, String routeId) { - //TODO: implement method + // Do nothing since we don't support multiple BT yet. } @Override public void transferToRoute(String sessionId, String routeId) { - //TODO: implement method + if (TextUtils.equals(routeId, mDefaultRoute.getId())) { + mBtRouteProvider.clearActiveDevices(); + } else { + mBtRouteProvider.setActiveDevice(routeId); + } } //TODO: implement method @@ -147,8 +151,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { public void requestUpdateVolume(String routeId, int delta) { } - void initializeRoutes() { - //TODO: adds necessary info + private void initializeDefaultRoute() { mDefaultRoute = new MediaRoute2Info.Builder( DEFAULT_ROUTE_ID, mContext.getResources().getText(R.string.default_audio_route_name).toString()) @@ -172,7 +175,9 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { // route yet. updateAudioRoutes(newAudioRoutes); } + } + private void initializeSessionInfo() { mBluetoothRoutes = mBtRouteProvider.getBluetoothRoutes(); MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder(); @@ -183,11 +188,15 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { setProviderState(builder.build()); mHandler.post(() -> notifyProviderState()); - // Note: No lock needed when initializing. - updateSessionInfosIfNeededLocked(); + //TODO: clean up this + // This is required because it is not instantiated in the main thread and + // BluetoothRoutesUpdatedListener can be called before this function + synchronized (mLock) { + updateSessionInfosIfNeededLocked(); + } } - void updateAudioRoutes(AudioRoutesInfo newRoutes) { + private void updateAudioRoutes(AudioRoutesInfo newRoutes) { int name = R.string.default_audio_route_name; mCurAudioRoutesInfo.mainType = newRoutes.mainType; if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADPHONES) != 0 @@ -226,15 +235,22 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { .setSystemSession(true); String activeBtDeviceAddress = mBtRouteProvider.getActiveDeviceAddress(); - RoutingSessionInfo newSessionInfo; if (!TextUtils.isEmpty(activeBtDeviceAddress)) { // Bluetooth route. Set the route ID with the device's address. - newSessionInfo = builder.addSelectedRoute(activeBtDeviceAddress).build(); + builder.addSelectedRoute(activeBtDeviceAddress); + builder.addTransferrableRoute(mDefaultRoute.getId()); } else { // Default device - newSessionInfo = builder.addSelectedRoute(mDefaultRoute.getId()).build(); + builder.addSelectedRoute(mDefaultRoute.getId()); } + for (MediaRoute2Info route : mBluetoothRoutes) { + if (!TextUtils.equals(activeBtDeviceAddress, route.getId())) { + builder.addTransferrableRoute(route.getId()); + } + } + + RoutingSessionInfo newSessionInfo = builder.setProviderId(mUniqueId).build(); if (Objects.equals(oldSessionInfo, newSessionInfo)) { return false; } else { @@ -244,11 +260,6 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { } } - /** - * The first route should be the currently selected system route. - * For example, if there are two system routes (BT and device speaker), - * BT will be the first route in the list. - */ void publishRoutes() { MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder(); builder.addRoute(mDefaultRoute); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index d0e4fe6e8a40..6f43952a3f58 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -378,8 +378,9 @@ public class NotificationManagerService extends SystemService { /** * Apps that post custom toasts in the background will have those blocked. Apps can - * still post toasts created with {@link Toast#makeText(Context, CharSequence, int)} and its - * variants while in the background. + * still post toasts created with + * {@link android.widget.Toast#makeText(Context, CharSequence, int)} and its variants while + * in the background. * * TODO(b/144152069): Add @EnabledAfter(Q) to target R+ after assessing impact on dogfood */ @@ -8255,7 +8256,8 @@ public class NotificationManagerService extends SystemService { record.getSystemGeneratedSmartActions(), record.getSmartReplies(), record.canBubble(), - record.isInterruptive() + record.isInterruptive(), + record.isConversation() ); rankings.add(ranking); } diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index f344a8464f10..2bea21891f8f 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -54,6 +54,7 @@ import android.service.notification.SnoozeCriterion; import android.service.notification.StatusBarNotification; import android.text.TextUtils; import android.util.ArraySet; +import android.util.FeatureFlagUtils; import android.util.Log; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; @@ -164,6 +165,7 @@ public final class NotificationRecord { private boolean mShowBadge; private boolean mAllowBubble; private Light mLight; + private boolean mIsNotConversationOverride; /** * This list contains system generated smart actions from NAS, app-generated smart actions are * stored in Notification.actions with isContextual() set to true. @@ -660,6 +662,10 @@ public final class NotificationRecord { if (signals.containsKey(Adjustment.KEY_RANKING_SCORE)) { mRankingScore = signals.getFloat(Adjustment.KEY_RANKING_SCORE); } + if (signals.containsKey(Adjustment.KEY_NOT_CONVERSATION)) { + mIsNotConversationOverride = signals.getBoolean( + Adjustment.KEY_NOT_CONVERSATION); + } if (!signals.isEmpty() && adjustment.getIssuer() != null) { mAdjustmentIssuer = adjustment.getIssuer(); } @@ -1310,6 +1316,25 @@ public final class NotificationRecord { return hasCustomRemoteView && !hasDecoratedStyle; } + /** Whether this notification is a conversation notification. */ + public boolean isConversation() { + Notification notification = getNotification(); + if (mChannel.isDemoted() + || !Notification.MessagingStyle.class.equals(notification.getNotificationStyle())) { + return false; + } + if (notification.getShortcutId() == null + && !FeatureFlagUtils.isEnabled( + mContext, FeatureFlagUtils.NOTIF_CONVO_BYPASS_SHORTCUT_REQ)) { + return false; + } + if (mIsNotConversationOverride) { + return false; + } + // STOPSHIP b/137397357: Check shortcut to make a further decision + return true; + } + @VisibleForTesting static final class Light { public final int color; diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index 28079099469a..c6d2b334bd71 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -66,7 +66,7 @@ import java.util.stream.Collectors; * ApexManager class handles communications with the apex service to perform operation and queries, * as well as providing caching to avoid unnecessary calls to the service. */ -abstract class ApexManager { +public abstract class ApexManager { private static final String TAG = "ApexManager"; @@ -265,6 +265,13 @@ abstract class ApexManager { abstract List<String> getApksInApex(String apexPackageName); /** + * Returns the apex module name for the given package name, if the package is an APEX. Otherwise + * returns {@code null}. + */ + @Nullable + public abstract String getApexModuleNameForPackageName(String apexPackageName); + + /** * Dumps various state information to the provided {@link PrintWriter} object. * * @param pw the {@link PrintWriter} object to send information to. @@ -646,6 +653,15 @@ abstract class ApexManager { } } + @Override + @Nullable + public String getApexModuleNameForPackageName(String apexPackageName) { + populatePackageNameToApexModuleNameIfNeeded(); + synchronized (mLock) { + return mPackageNameToApexModuleName.get(apexPackageName); + } + } + /** * Dump information about the packages contained in a particular cache * @param packagesCache the cache to print information about. @@ -843,6 +859,12 @@ abstract class ApexManager { } @Override + @Nullable + public String getApexModuleNameForPackageName(String apexPackageName) { + return null; + } + + @Override void dump(PrintWriter pw, String packageName) { // No-op } diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java index 3ed353483068..78e17192ed58 100644 --- a/services/core/java/com/android/server/pm/AppsFilter.java +++ b/services/core/java/com/android/server/pm/AppsFilter.java @@ -213,6 +213,25 @@ public class AppsFilter { return false; } + private static boolean canQueryViaPackage(AndroidPackage querying, + AndroidPackage potentialTarget) { + return querying.getQueriesPackages() != null + && querying.getQueriesPackages().contains(potentialTarget.getPackageName()); + } + + private static boolean canQueryAsInstaller(PackageSetting querying, + AndroidPackage potentialTarget) { + final InstallSource installSource = querying.installSource; + if (potentialTarget.getPackageName().equals(installSource.installerPackageName)) { + return true; + } + if (!installSource.isInitiatingPackageUninstalled + && potentialTarget.getPackageName().equals(installSource.initiatingPackageName)) { + return true; + } + return false; + } + private static boolean matches(Intent intent, AndroidPackage potentialTarget) { for (int p = ArrayUtils.size(potentialTarget.getProviders()) - 1; p >= 0; p--) { ParsedProvider provider = potentialTarget.getProviders().get(p); @@ -331,8 +350,8 @@ public class AppsFilter { if (canQueryViaIntent(existingPkg, newPkg)) { mQueriesViaIntent.add(existingSetting.appId, newPkgSetting.appId); } - if (existingPkg.getQueriesPackages() != null - && existingPkg.getQueriesPackages().contains(newPkg.getPackageName())) { + if (canQueryViaPackage(existingPkg, newPkg) + || canQueryAsInstaller(existingSetting, newPkg)) { mQueriesViaPackage.add(existingSetting.appId, newPkgSetting.appId); } } @@ -341,8 +360,8 @@ public class AppsFilter { if (canQueryViaIntent(newPkg, existingPkg)) { mQueriesViaIntent.add(newPkgSetting.appId, existingSetting.appId); } - if (newPkg.getQueriesPackages() != null - && newPkg.getQueriesPackages().contains(existingPkg.getPackageName())) { + if (canQueryViaPackage(newPkg, existingPkg) + || canQueryAsInstaller(newPkgSetting, existingPkg)) { mQueriesViaPackage.add(newPkgSetting.appId, existingSetting.appId); } } @@ -535,7 +554,6 @@ public class AppsFilter { try { Trace.beginSection("mQueriesViaPackage"); if (mQueriesViaPackage.contains(callingAppId, targetAppId)) { - // the calling package has explicitly declared the target package; allow if (DEBUG_LOGGING) { log(callingSetting, targetPkgSetting, "queries package"); } diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java index fc08ddeddf82..74d2efeceb63 100644 --- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java +++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java @@ -286,21 +286,21 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { } private List<UserHandle> getTargetUserProfilesUnchecked( - String callingPackage, @UserIdInt int callingUserId) { + String packageName, @UserIdInt int userId) { final long ident = mInjector.clearCallingIdentity(); try { final int[] enabledProfileIds = - mInjector.getUserManager().getEnabledProfileIds(callingUserId); + mInjector.getUserManager().getEnabledProfileIds(userId); List<UserHandle> targetProfiles = new ArrayList<>(); - for (final int userId : enabledProfileIds) { - if (userId == callingUserId) { + for (final int profileId : enabledProfileIds) { + if (profileId == userId) { continue; } - if (!isPackageEnabled(callingPackage, userId)) { + if (!isPackageEnabled(packageName, profileId)) { continue; } - targetProfiles.add(UserHandle.of(userId)); + targetProfiles.add(UserHandle.of(profileId)); } return targetProfiles; } finally { @@ -385,14 +385,9 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { "INTERACT_ACROSS_USERS or INTERACT_ACROSS_USERS_FULL is required to set the" + " app-op for interacting across profiles."); } - if (!isPermissionGranted(Manifest.permission.MANAGE_APP_OPS_MODES, callingUid)) { - throw new SecurityException( - "MANAGE_APP_OPS_MODES is required to set the app-op for interacting across" - + " profiles."); - } final int callingUserId = mInjector.getCallingUserId(); if (newMode == AppOpsManager.MODE_ALLOWED - && !canRequestInteractAcrossProfilesUnchecked(packageName, callingUserId)) { + && !canConfigureInteractAcrossProfiles(packageName)) { // The user should not be prompted for apps that cannot request to interact across // profiles. However, we return early here if required to avoid race conditions. Slog.e(TAG, "Tried to turn on the appop for interacting across profiles for invalid" @@ -441,7 +436,7 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { final int uid = mInjector.getPackageManager() .getPackageUidAsUser(packageName, /* flags= */ 0, userId); if (currentModeEquals(newMode, packageName, uid)) { - Slog.w(TAG,"Attempt to set mode to existing value of " + newMode + " for " + Slog.w(TAG, "Attempt to set mode to existing value of " + newMode + " for " + packageName + " on user ID " + userId); return; } @@ -462,18 +457,73 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { private void sendCanInteractAcrossProfilesChangedBroadcast( String packageName, int uid, UserHandle userHandle) { - final Intent intent = new Intent(ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED) - .setPackage(packageName); - if (!appDeclaresCrossProfileAttribute(uid)) { + final Intent intent = + new Intent(ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED).setPackage(packageName); + if (appDeclaresCrossProfileAttribute(uid)) { + intent.addFlags( + Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND | Intent.FLAG_RECEIVER_FOREGROUND); + } else { intent.addFlags(FLAG_RECEIVER_REGISTERED_ONLY); } - mInjector.sendBroadcastAsUser(intent, userHandle); + for (ResolveInfo receiver : findBroadcastReceiversForUser(intent, userHandle)) { + intent.setComponent(receiver.getComponentInfo().getComponentName()); + mInjector.sendBroadcastAsUser(intent, userHandle); + } + } + + private List<ResolveInfo> findBroadcastReceiversForUser(Intent intent, UserHandle userHandle) { + return mInjector.getPackageManager() + .queryBroadcastReceiversAsUser(intent, /* flags= */ 0, userHandle); } private boolean appDeclaresCrossProfileAttribute(int uid) { return mInjector.getPackageManagerInternal().getPackage(uid).isCrossProfile(); } + @Override + public boolean canConfigureInteractAcrossProfiles(String packageName) { + if (!hasOtherProfileWithPackageInstalled(packageName, mInjector.getCallingUserId())) { + return false; + } + if (!hasRequestedAppOpPermission( + AppOpsManager.opToPermission(OP_INTERACT_ACROSS_PROFILES), packageName)) { + return false; + } + return isCrossProfilePackageWhitelisted(packageName); + } + + private boolean hasOtherProfileWithPackageInstalled(String packageName, @UserIdInt int userId) { + final long ident = mInjector.clearCallingIdentity(); + try { + final int[] profileIds = + mInjector.getUserManager().getProfileIds(userId, /* enabledOnly= */ false); + for (int profileId : profileIds) { + if (profileId != userId && isPackageInstalled(packageName, profileId)) { + return true; + } + } + } finally { + mInjector.restoreCallingIdentity(ident); + } + return false; + } + + @Override + public void resetInteractAcrossProfilesAppOps(List<String> packageNames) { + packageNames.forEach(this::resetInteractAcrossProfilesAppOp); + } + + private void resetInteractAcrossProfilesAppOp(String packageName) { + if (canConfigureInteractAcrossProfiles(packageName)) { + Slog.w(TAG, "Not resetting app-op for package " + packageName + + " since it is still configurable by users."); + return; + } + final String op = + AppOpsManager.permissionToOp(Manifest.permission.INTERACT_ACROSS_PROFILES); + setInteractAcrossProfilesAppOp(packageName, AppOpsManager.opToDefaultMode(op)); + } + private boolean isSameProfileGroup(@UserIdInt int callerUserId, @UserIdInt int userId) { final long ident = mInjector.clearCallingIdentity(); try { diff --git a/services/core/java/com/android/server/pm/ModuleInfoProvider.java b/services/core/java/com/android/server/pm/ModuleInfoProvider.java index 69510d9e5565..06706cd06e11 100644 --- a/services/core/java/com/android/server/pm/ModuleInfoProvider.java +++ b/services/core/java/com/android/server/pm/ModuleInfoProvider.java @@ -57,9 +57,9 @@ public class ModuleInfoProvider { */ private static final String MODULE_METADATA_KEY = "android.content.pm.MODULE_METADATA"; - private final Context mContext; private final IPackageManager mPackageManager; + private final ApexManager mApexManager; private final Map<String, ModuleInfo> mModuleInfo; // TODO: Move this to an earlier boot phase if anybody requires it then. @@ -69,13 +69,16 @@ public class ModuleInfoProvider { ModuleInfoProvider(Context context, IPackageManager packageManager) { mContext = context; mPackageManager = packageManager; + mApexManager = ApexManager.getInstance(); mModuleInfo = new ArrayMap<>(); } @VisibleForTesting - public ModuleInfoProvider(XmlResourceParser metadata, Resources resources) { + public ModuleInfoProvider( + XmlResourceParser metadata, Resources resources, ApexManager apexManager) { mContext = null; mPackageManager = null; + mApexManager = apexManager; mModuleInfo = new ArrayMap<>(); loadModuleMetadata(metadata, resources); } @@ -150,6 +153,8 @@ public class ModuleInfoProvider { mi.setHidden(isHidden); mi.setPackageName(modulePackageName); mi.setName(moduleName); + mi.setApexModuleName( + mApexManager.getApexModuleNameForPackageName(modulePackageName)); mModuleInfo.put(modulePackageName, mi); } @@ -167,7 +172,7 @@ public class ModuleInfoProvider { * * @param flags Use {@link PackageManager#MATCH_ALL} flag to get all modules. */ - List<ModuleInfo> getInstalledModules(@PackageManager.ModuleInfoFlags int flags) { + List<ModuleInfo> getInstalledModules(@PackageManager.InstalledModulesFlags int flags) { if (!mMetadataLoaded) { throw new IllegalStateException("Call to getInstalledModules before metadata loaded"); } @@ -195,12 +200,19 @@ public class ModuleInfoProvider { return installedModules; } - ModuleInfo getModuleInfo(String packageName, int flags) { + ModuleInfo getModuleInfo(String name, @PackageManager.ModuleInfoFlags int flags) { if (!mMetadataLoaded) { throw new IllegalStateException("Call to getModuleInfo before metadata loaded"); } - - return mModuleInfo.get(packageName); + if ((flags & PackageManager.MODULE_APEX_NAME) != 0) { + for (ModuleInfo moduleInfo : mModuleInfo.values()) { + if (name.equals(moduleInfo.getApexModuleName())) { + return moduleInfo; + } + } + return null; + } + return mModuleInfo.get(name); } String getPackageName() { diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index b1c38d1ebed4..10f46fd808c8 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -259,11 +259,13 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements // atomic install which needs to query sessions, which requires lock on mSessions. boolean isDeviceUpgrading = mPm.isDeviceUpgrading(); for (PackageInstallerSession session : stagedSessionsToRestore) { - if (isDeviceUpgrading && !session.isStagedAndInTerminalState()) { + if (!session.isStagedAndInTerminalState() && session.hasParentSessionId() + && getSession(session.getParentSessionId()) == null) { session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, - "Build fingerprint has changed"); + "An orphan staged session " + session.sessionId + " is found, " + + "parent " + session.getParentSessionId() + " is missing"); } - mStagingManager.restoreSession(session); + mStagingManager.restoreSession(session, isDeviceUpgrading); } // Broadcasts are not sent while we restore sessions on boot, since no processes would be // ready to listen to them. From now on, we greedily assume that broadcasts requests are @@ -400,10 +402,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } finally { IoUtils.closeQuietly(fis); } - // Re-sealing the sealed sessions. + // After all of the sessions were loaded, they are ready to be sealed and validated for (int i = 0; i < mSessions.size(); ++i) { PackageInstallerSession session = mSessions.valueAt(i); - session.sealIfNecessary(); + session.sealAndValidateIfNecessary(); } } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 78875da9e790..124bbf52561a 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -597,6 +597,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { info.isStagedSessionReady = mStagedSessionReady; info.isStagedSessionFailed = mStagedSessionFailed; info.setStagedSessionErrorCode(mStagedSessionErrorCode, mStagedSessionErrorMessage); + info.createdMillis = createdMillis; info.updatedMillis = updatedMillis; } return info; @@ -1360,13 +1361,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } /** - * If session should be sealed, then it's sealed to prevent further modification. - * If the session can't be sealed then it's destroyed. + * If session should be sealed, then it's sealed to prevent further modification + * and then it's validated. + * + * If the session was sealed but something went wrong then it's destroyed. * * <p> This is meant to be called after all of the sessions are loaded and added to * PackageInstallerService */ - void sealIfNecessary() { + void sealAndValidateIfNecessary() { synchronized (mLock) { if (!mShouldBeSealed || isStagedAndInTerminalState()) { return; @@ -1375,7 +1378,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { List<PackageInstallerSession> childSessions = getChildSessions(); synchronized (mLock) { try { - sealLocked(childSessions); + sealAndValidateLocked(childSessions); + } catch (StreamingException e) { + Slog.e(TAG, "Streaming failed", e); } catch (PackageManagerException e) { Slog.e(TAG, "Package not valid", e); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 07aec4ad41d4..afd9e0956048 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -185,6 +185,7 @@ import android.content.pm.PackageUserState; import android.content.pm.ParceledListSlice; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; +import android.content.pm.ProcessInfo; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.SELinuxUtil; @@ -3158,6 +3159,7 @@ public class PackageManagerService extends IPackageManager.Stub // Adjust seInfo to ensure apps which share a sharedUserId are placed in the same // SELinux domain. setting.fixSeInfoLocked(); + setting.updateProcesses(); } // Now that we know all the packages we are keeping, @@ -13494,6 +13496,7 @@ public class PackageManagerService extends IPackageManager.Stub // Okay! targetPackageSetting.setInstallerPackageName(installerPackageName); mSettings.addInstallerPackageNames(targetPackageSetting.installSource); + mAppsFilter.addPackage(targetPackageSetting, mSettings.mPackages); scheduleWriteSettingsLocked(); } } @@ -23486,6 +23489,20 @@ public class PackageManagerService extends IPackageManager.Stub } @Override + public ArrayMap<String, ProcessInfo> getProcessesForUid(int uid) { + synchronized (mLock) { + return getProcessesForUidLocked(uid); + } + } + + @Override + public int[] getPermissionGids(String permissionName, int userId) { + synchronized (mLock) { + return getPermissionGidsLocked(permissionName, userId); + } + } + + @Override public boolean isOnlyCoreApps() { return PackageManagerService.this.isOnlyCoreApps(); } @@ -23740,6 +23757,30 @@ public class PackageManagerService extends IPackageManager.Stub return res != null ? res : EmptyArray.STRING; } + @GuardedBy("mLock") + public ArrayMap<String, ProcessInfo> getProcessesForUidLocked(int uid) { + final int appId = UserHandle.getAppId(uid); + final SettingBase obj = mSettings.getSettingLPr(appId); + if (obj instanceof SharedUserSetting) { + final SharedUserSetting sus = (SharedUserSetting) obj; + return PackageInfoUtils.generateProcessInfo(sus.processes, 0); + } else if (obj instanceof PackageSetting) { + final PackageSetting ps = (PackageSetting) obj; + return PackageInfoUtils.generateProcessInfo(ps.pkg.getProcesses(), 0); + } + return null; + } + + @GuardedBy("mLock") + public int[] getPermissionGidsLocked(String permissionName, int userId) { + BasePermission perm + = mPermissionManager.getPermissionSettings().getPermission(permissionName); + if (perm != null) { + return perm.computeGids(userId); + } + return null; + } + @Override public int getRuntimePermissionsVersion(@UserIdInt int userId) { Preconditions.checkArgumentNonnegative(userId); diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java index 0a42ccf1c5ba..b9bb9e0ad0d8 100644 --- a/services/core/java/com/android/server/pm/SharedUserSetting.java +++ b/services/core/java/com/android/server/pm/SharedUserSetting.java @@ -19,7 +19,9 @@ package com.android.server.pm; import android.annotation.Nullable; import android.content.pm.ApplicationInfo; import android.content.pm.parsing.AndroidPackage; +import android.content.pm.parsing.ComponentParseUtils; import android.service.pm.PackageServiceDumpProto; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.proto.ProtoOutputStream; @@ -51,6 +53,8 @@ public final class SharedUserSetting extends SettingBase { final PackageSignatures signatures = new PackageSignatures(); Boolean signaturesChanged; + ArrayMap<String, ComponentParseUtils.ParsedProcess> processes; + SharedUserSetting(String _name, int _pkgFlags, int _pkgPrivateFlags) { super(_pkgFlags, _pkgPrivateFlags); uidFlags = _pkgFlags; @@ -72,6 +76,25 @@ public final class SharedUserSetting extends SettingBase { proto.end(token); } + void addProcesses(ArrayMap<String, ComponentParseUtils.ParsedProcess> newProcs) { + if (newProcs != null) { + final int numProcs = newProcs.size(); + if (processes == null) { + processes = new ArrayMap<>(numProcs); + } + for (int i = 0; i < numProcs; i++) { + ComponentParseUtils.ParsedProcess newProc = newProcs.valueAt(i); + ComponentParseUtils.ParsedProcess proc = processes.get(newProc.name); + if (proc == null) { + proc = new ComponentParseUtils.ParsedProcess(newProc); + processes.put(newProc.name, proc); + } else { + proc.addStateFrom(newProc); + } + } + } + } + boolean removePackage(PackageSetting packageSetting) { if (!packages.remove(packageSetting)) { return false; @@ -91,6 +114,8 @@ public final class SharedUserSetting extends SettingBase { } setPrivateFlags(aggregatedPrivateFlags); } + // recalculate processes. + updateProcesses(); return true; } @@ -104,6 +129,9 @@ public final class SharedUserSetting extends SettingBase { setFlags(this.pkgFlags | packageSetting.pkgFlags); setPrivateFlags(this.pkgPrivateFlags | packageSetting.pkgPrivateFlags); } + if (packageSetting.pkg != null) { + addProcesses(packageSetting.pkg.getProcesses()); + } } public @Nullable List<AndroidPackage> getPackages() { @@ -148,6 +176,16 @@ public final class SharedUserSetting extends SettingBase { } } + /** + * Update tracked data about processes based on all known packages in the shared user ID. + */ + public void updateProcesses() { + processes = null; + for (int i = packages.size() - 1; i >= 0; i--) { + addProcesses(packages.valueAt(i).pkg.getProcesses()); + } + } + /** Returns userIds which doesn't have any packages with this sharedUserId */ public int[] getNotInstalledUserIds() { int[] excludedUserIds = null; @@ -176,6 +214,17 @@ public final class SharedUserSetting extends SettingBase { this.packages.clear(); this.packages.addAll(sharedUser.packages); this.signaturesChanged = sharedUser.signaturesChanged; + if (sharedUser.processes != null) { + final int numProcs = sharedUser.processes.size(); + this.processes = new ArrayMap<>(numProcs); + for (int i = 0; i < numProcs; i++) { + ComponentParseUtils.ParsedProcess proc = + new ComponentParseUtils.ParsedProcess(sharedUser.processes.valueAt(i)); + this.processes.put(proc.name, proc); + } + } else { + this.processes = null; + } return this; } } diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 7888d1f9612f..74c98f9ef444 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -889,7 +889,7 @@ public class StagingManager { return false; } - void restoreSession(@NonNull PackageInstallerSession session) { + void restoreSession(@NonNull PackageInstallerSession session, boolean isDeviceUpgrading) { PackageInstallerSession sessionToResume = session; synchronized (mStagedSessions) { mStagedSessions.append(session.sessionId, session); @@ -906,6 +906,13 @@ public class StagingManager { } } } + // The preconditions used during pre-reboot verification might have changed when device + // is upgrading. Updated staged sessions to activation failed before we resume the session. + if (isDeviceUpgrading && !sessionToResume.isStagedAndInTerminalState()) { + sessionToResume.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, + "Build fingerprint has changed"); + return; + } checkStateAndResume(sessionToResume); } diff --git a/services/core/java/com/android/server/policy/GlobalKeyManager.java b/services/core/java/com/android/server/policy/GlobalKeyManager.java index e08c004866ea..157f8256ce50 100644 --- a/services/core/java/com/android/server/policy/GlobalKeyManager.java +++ b/services/core/java/com/android/server/policy/GlobalKeyManager.java @@ -74,7 +74,7 @@ final class GlobalKeyManager { Intent intent = new Intent(Intent.ACTION_GLOBAL_BUTTON) .setComponent(component) .setFlags(Intent.FLAG_RECEIVER_FOREGROUND) - .putExtra(Intent.EXTRA_KEY_EVENT, event); + .putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(event)); context.sendBroadcastAsUser(intent, UserHandle.CURRENT, null); return true; } diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java index 8460ede91fd0..df8e30f9387a 100644 --- a/services/core/java/com/android/server/power/Notifier.java +++ b/services/core/java/com/android/server/power/Notifier.java @@ -46,6 +46,7 @@ import android.os.VibrationEffect; import android.os.Vibrator; import android.os.WorkSource; import android.provider.Settings; +import android.telephony.TelephonyManager; import android.util.EventLog; import android.util.Slog; import android.util.StatsLog; @@ -671,6 +672,8 @@ public class Notifier { } mUserActivityPending = false; } + TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); + tm.notifyUserActivity(); mPolicy.userActivity(); } diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index c1b71aab38fd..ca368694ca92 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -16,6 +16,8 @@ package com.android.server.power; +import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_DEFAULT; +import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL; import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP; import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE; import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING; @@ -72,6 +74,7 @@ import android.provider.Settings.SettingNotFoundException; import android.service.dreams.DreamManagerInternal; import android.service.vr.IVrManager; import android.service.vr.IVrStateCallbacks; +import android.util.ArraySet; import android.util.KeyValueListParser; import android.util.PrintWriterPrinter; import android.util.Slog; @@ -109,6 +112,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.Set; /** * The power manager service is responsible for coordinating power management @@ -565,6 +569,9 @@ public final class PowerManagerService extends SystemService // but the DreamService has not yet been told to start (it's an async process). private boolean mDozeStartInProgress; + // Set of all tokens suppressing ambient display. + private final Set<String> mAmbientDisplaySuppressionTokens = new ArraySet<>(); + private final class ForegroundProfileObserver extends SynchronousUserSwitchObserver { @Override public void onUserSwitching(@UserIdInt int newUserId) throws RemoteException { @@ -844,7 +851,8 @@ public final class PowerManagerService extends SystemService @Override public void onStart() { - publishBinderService(Context.POWER_SERVICE, mBinderService); + publishBinderService(Context.POWER_SERVICE, mBinderService, /* allowIsolated= */ false, + DUMP_FLAG_PRIORITY_DEFAULT | DUMP_FLAG_PRIORITY_CRITICAL); publishLocalService(PowerManagerInternal.class, mLocalService); Watchdog.getInstance().addMonitor(this); @@ -3357,6 +3365,26 @@ public final class PowerManagerService extends SystemService } } + private void suppressAmbientDisplayInternal(String token, boolean suppress) { + if (DEBUG_SPEW) { + Slog.d(TAG, "Suppress ambient display for token " + token + ": " + suppress); + } + + if (suppress) { + mAmbientDisplaySuppressionTokens.add(token); + } else { + mAmbientDisplaySuppressionTokens.remove(token); + } + + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.SUPPRESS_DOZE, + Math.min(mAmbientDisplaySuppressionTokens.size(), 1)); + } + + private String createAmbientDisplayToken(String token, int callingUid) { + return callingUid + "_" + token; + } + private void boostScreenBrightnessInternal(long eventTime, int uid) { synchronized (mLock) { if (!mSystemReady || mWakefulness == WAKEFULNESS_ASLEEP @@ -5007,6 +5035,61 @@ public final class PowerManagerService extends SystemService } @Override // Binder call + public boolean isAmbientDisplayAvailable() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.READ_DREAM_STATE, null); + + final long ident = Binder.clearCallingIdentity(); + try { + return mAmbientDisplayConfiguration.ambientDisplayAvailable(); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call + public void suppressAmbientDisplay(@NonNull String token, boolean suppress) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.WRITE_DREAM_STATE, null); + + final int uid = Binder.getCallingUid(); + final long ident = Binder.clearCallingIdentity(); + try { + suppressAmbientDisplayInternal(createAmbientDisplayToken(token, uid), suppress); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call + public boolean isAmbientDisplaySuppressedForToken(@NonNull String token) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.READ_DREAM_STATE, null); + + final int uid = Binder.getCallingUid(); + final long ident = Binder.clearCallingIdentity(); + try { + return mAmbientDisplaySuppressionTokens.contains( + createAmbientDisplayToken(token, uid)); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call + public boolean isAmbientDisplaySuppressed() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.READ_DREAM_STATE, null); + + final long ident = Binder.clearCallingIdentity(); + try { + return mAmbientDisplaySuppressionTokens.size() > 0; + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call public void boostScreenBrightness(long eventTime) { if (eventTime > SystemClock.uptimeMillis()) { throw new IllegalArgumentException("event time must not be in the future"); diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java index 6686de95c05c..951f1a4663f1 100644 --- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java +++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java @@ -28,6 +28,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.VersionedPackage; @@ -45,20 +46,21 @@ import android.util.ArraySet; import android.util.Slog; import android.util.StatsLog; -import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.server.PackageWatchdog; import com.android.server.PackageWatchdog.FailureReasons; import com.android.server.PackageWatchdog.PackageHealthObserver; import com.android.server.PackageWatchdog.PackageHealthObserverImpact; -import libcore.io.IoUtils; - +import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; +import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.Set; @@ -72,11 +74,12 @@ import java.util.Set; public final class RollbackPackageHealthObserver implements PackageHealthObserver { private static final String TAG = "RollbackPackageHealthObserver"; private static final String NAME = "rollback-observer"; - private static final int INVALID_ROLLBACK_ID = -1; + + private static final String LOGGING_PARENT_KEY = "android.content.pm.LOGGING_PARENT"; private final Context mContext; private final Handler mHandler; - private final File mLastStagedRollbackIdFile; + private final File mLastStagedRollbackIdsFile; // Staged rollback ids that have been committed but their session is not yet ready @GuardedBy("mPendingStagedRollbackIds") private final Set<Integer> mPendingStagedRollbackIds = new ArraySet<>(); @@ -88,7 +91,7 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve mHandler = handlerThread.getThreadHandler(); File dataDir = new File(Environment.getDataDirectory(), "rollback-observer"); dataDir.mkdirs(); - mLastStagedRollbackIdFile = new File(dataDir, "last-staged-rollback-id"); + mLastStagedRollbackIdsFile = new File(dataDir, "last-staged-rollback-ids"); PackageWatchdog.getInstance(mContext).registerHealthObserver(this); } @@ -150,22 +153,25 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve private void onBootCompleted() { RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class); - PackageInstaller packageInstaller = mContext.getPackageManager().getPackageInstaller(); - String moduleMetadataPackageName = getModuleMetadataPackageName(); - if (!rollbackManager.getAvailableRollbacks().isEmpty()) { // TODO(gavincorkery): Call into Package Watchdog from outside the observer PackageWatchdog.getInstance(mContext).scheduleCheckAndMitigateNativeCrashes(); } - int rollbackId = popLastStagedRollbackId(); - if (rollbackId == INVALID_ROLLBACK_ID) { - // No staged rollback before reboot - return; + List<Integer> rollbackIds = popLastStagedRollbackIds(); + Iterator<Integer> rollbackIterator = rollbackIds.iterator(); + while (rollbackIterator.hasNext()) { + int rollbackId = rollbackIterator.next(); + logRollbackStatusOnBoot(rollbackId, rollbackManager.getRecentlyCommittedRollbacks()); } + } + + private void logRollbackStatusOnBoot(int rollbackId, + List<RollbackInfo> recentlyCommittedRollbacks) { + PackageInstaller packageInstaller = mContext.getPackageManager().getPackageInstaller(); RollbackInfo rollback = null; - for (RollbackInfo info : rollbackManager.getRecentlyCommittedRollbacks()) { + for (RollbackInfo info : recentlyCommittedRollbacks) { if (rollbackId == info.getRollbackId()) { rollback = info; break; @@ -177,13 +183,26 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve return; } - // Use the version of the metadata package that was installed before + // Identify the logging parent for this rollback. When all configurations are correct, each + // package in the rollback refers to the same logging parent, except for the logging parent + // itself. If a logging parent is missing for a package, we use the package itself for + // logging. This might result in over-logging, but we prefer this over no logging. + final Set<String> loggingPackageNames = new ArraySet<>(); + for (PackageRollbackInfo packageRollback : rollback.getPackages()) { + final String loggingParentName = getLoggingParentName(packageRollback.getPackageName()); + if (loggingParentName != null) { + loggingPackageNames.add(loggingParentName); + } else { + loggingPackageNames.add(packageRollback.getPackageName()); + } + } + + // Use the version of the logging parent that was installed before // we rolled back for logging purposes. - VersionedPackage oldLogPackage = null; + final List<VersionedPackage> oldLoggingPackages = new ArrayList<>(); for (PackageRollbackInfo packageRollback : rollback.getPackages()) { - if (packageRollback.getPackageName().equals(moduleMetadataPackageName)) { - oldLogPackage = packageRollback.getVersionRolledBackFrom(); - break; + if (loggingPackageNames.contains(packageRollback.getPackageName())) { + oldLoggingPackages.add(packageRollback.getVersionRolledBackFrom()); } } @@ -193,16 +212,17 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve Slog.e(TAG, "On boot completed, could not load session id " + sessionId); return; } - if (sessionInfo.isStagedSessionApplied()) { - 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(oldLogPackage, - StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE, - WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN, ""); + + for (VersionedPackage oldLoggingPackage : oldLoggingPackages) { + if (sessionInfo.isStagedSessionApplied()) { + logEvent(oldLoggingPackage, + StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS, + WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN, ""); + } else if (sessionInfo.isStagedSessionFailed()) { + logEvent(oldLoggingPackage, + StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE, + WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN, ""); + } } } @@ -236,27 +256,17 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve } @Nullable - private String getModuleMetadataPackageName() { - String packageName = mContext.getResources().getString( - R.string.config_defaultModuleMetadataProvider); - if (TextUtils.isEmpty(packageName)) { - return null; - } - return packageName; - } - - @Nullable - private VersionedPackage getModuleMetadataPackage() { - String packageName = getModuleMetadataPackageName(); - if (packageName == null) { - return null; - } - + private String getLoggingParentName(String packageName) { + PackageManager packageManager = mContext.getPackageManager(); try { - return new VersionedPackage(packageName, mContext.getPackageManager().getPackageInfo( - packageName, 0 /* flags */).getLongVersionCode()); - } catch (PackageManager.NameNotFoundException e) { - Slog.w(TAG, "Module metadata provider not found"); + ApplicationInfo ai = packageManager.getApplicationInfo(packageName, + PackageManager.GET_META_DATA); + if (ai.metaData == null) { + return null; + } + return ai.metaData.getString(LOGGING_PARENT_KEY); + } catch (Exception e) { + Slog.w(TAG, "Unable to discover logging parent package: " + packageName, e); return null; } } @@ -294,7 +304,7 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve 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); + saveStagedRollbackId(rollbackId); } logEvent(logPackage, StatsLog @@ -338,34 +348,39 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve } } - private void saveLastStagedRollbackId(int stagedRollbackId) { + private void saveStagedRollbackId(int stagedRollbackId) { try { - FileOutputStream fos = new FileOutputStream(mLastStagedRollbackIdFile); + FileOutputStream fos = new FileOutputStream( + mLastStagedRollbackIdsFile, /*append*/true); PrintWriter pw = new PrintWriter(fos); - pw.println(stagedRollbackId); + pw.append(",").append(String.valueOf(stagedRollbackId)); pw.flush(); FileUtils.sync(fos); pw.close(); } catch (IOException e) { Slog.e(TAG, "Failed to save last staged rollback id", e); - mLastStagedRollbackIdFile.delete(); + mLastStagedRollbackIdsFile.delete(); } } - private int popLastStagedRollbackId() { - int rollbackId = INVALID_ROLLBACK_ID; - if (!mLastStagedRollbackIdFile.exists()) { - return rollbackId; - } - - try { - rollbackId = Integer.parseInt( - IoUtils.readFileAsString(mLastStagedRollbackIdFile.getAbsolutePath()).trim()); - } catch (IOException | NumberFormatException e) { - Slog.e(TAG, "Failed to retrieve last staged rollback id", e); + private List<Integer> popLastStagedRollbackIds() { + try (BufferedReader reader = + new BufferedReader(new FileReader(mLastStagedRollbackIdsFile))) { + String line = reader.readLine(); + // line is of format : ",id1,id2,id3....,idn" + String[] sessionIdsStr = line.split(","); + ArrayList<Integer> result = new ArrayList<>(); + for (String sessionIdStr: sessionIdsStr) { + if (!TextUtils.isEmpty(sessionIdStr.trim())) { + result.add(Integer.parseInt(sessionIdStr)); + } + } + return result; + } catch (Exception ignore) { + return Collections.emptyList(); + } finally { + mLastStagedRollbackIdsFile.delete(); } - mLastStagedRollbackIdFile.delete(); - return rollbackId; } private static String rollbackTypeToString(int type) { @@ -383,16 +398,33 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve } } + private static String rollbackReasonToString(int reason) { + switch (reason) { + case StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH: + return "REASON_NATIVE_CRASH"; + case StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK: + return "REASON_EXPLICIT_HEALTH_CHECK"; + case StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH: + return "REASON_APP_CRASH"; + case StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING: + return "REASON_APP_NOT_RESPONDING"; + default: + return "UNKNOWN"; + } + } + private static void logEvent(@Nullable VersionedPackage logPackage, int type, int rollbackReason, @NonNull String failingPackageName) { - Slog.i(TAG, "Watchdog event occurred of type: " + rollbackTypeToString(type)); + Slog.i(TAG, "Watchdog event occurred with type: " + rollbackTypeToString(type) + + " logPackage: " + logPackage + + " rollbackReason: " + rollbackReasonToString(rollbackReason) + + " failedPackageName: " + failingPackageName); if (logPackage != null) { StatsLog.logWatchdogRollbackOccurred(type, logPackage.getPackageName(), - logPackage.getVersionCode(), rollbackReason, failingPackageName); + logPackage.getLongVersionCode(), rollbackReason, failingPackageName); } } - /** * Returns true if the package name is the name of a module. */ @@ -422,10 +454,21 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve } else { failedPackageToLog = failedPackage.getPackageName(); } - final VersionedPackage logPackage = isModule(failedPackage.getPackageName()) - ? getModuleMetadataPackage() - : null; + VersionedPackage logPackageTemp = null; + if (isModule(failedPackage.getPackageName())) { + String logPackageName = getLoggingParentName(failedPackage.getPackageName()); + if (logPackageName == null) { + logPackageName = failedPackage.getPackageName(); + } + try { + logPackageTemp = new VersionedPackage(logPackageName, mContext.getPackageManager() + .getPackageInfo(logPackageName, 0 /* flags */).getLongVersionCode()); + } catch (PackageManager.NameNotFoundException e) { + logPackageTemp = null; + } + } + final VersionedPackage logPackage = logPackageTemp; logEvent(logPackage, StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE, reasonToLog, failedPackageToLog); diff --git a/services/core/java/com/android/server/utils/FlagNamespaceUtils.java b/services/core/java/com/android/server/utils/FlagNamespaceUtils.java deleted file mode 100644 index f8c7447fc55d..000000000000 --- a/services/core/java/com/android/server/utils/FlagNamespaceUtils.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.utils; - -import android.annotation.Nullable; -import android.provider.DeviceConfig; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.server.RescueParty; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * Utilities for interacting with the {@link android.provider.DeviceConfig}. - * - * @hide - */ -public final class FlagNamespaceUtils { - /** - * Special String used for communicating through {@link #RESET_PLATFORM_PACKAGE_FLAG} that - * Settings were reset by the RescueParty, no actual namespace with this name exists in - * {@link DeviceConfig}. - */ - public static final String NAMESPACE_NO_PACKAGE = "no_package"; - - /** - * Name of the special namespace in DeviceConfig table used for communicating resets. - */ - @VisibleForTesting - public static final String NAMESPACE_RESCUE_PARTY = "rescue_party_namespace"; - /** - * Flag in the {@link DeviceConfig} in {@link #NAMESPACE_RESCUE_PARTY}, holding all known {@link - * DeviceConfig} namespaces, as a {@link #DELIMITER} separated String. It's updated after the - * first time flags are written to the new namespace in the {@link DeviceConfig}. - */ - @VisibleForTesting - public static final String ALL_KNOWN_NAMESPACES_FLAG = "all_known_namespaces"; - /** - * Flag in the {@link DeviceConfig} in {@link #NAMESPACE_RESCUE_PARTY} with integer counter - * suffix added to it, holding {@link DeviceConfig} namespace value whose flags were recently - * reset by the {@link RescueParty}. It's updated by {@link RescueParty} every time given - * namespace flags are reset. - */ - @VisibleForTesting - public static final String RESET_PLATFORM_PACKAGE_FLAG = "reset_platform_package"; - private static final String DELIMITER = ":"; - /** - * Maximum value of the counter used in combination with {@link #RESET_PLATFORM_PACKAGE_FLAG} - * when communicating recently reset by the RescueParty namespace values. - */ - private static final int MAX_COUNTER_VALUE = 50; - - private static int sKnownResetNamespacesFlagCounter = -1; - - /** - * Sets the union of {@link #RESET_PLATFORM_PACKAGE_FLAG} with - * {@link #sKnownResetNamespacesFlagCounter} in the DeviceConfig for each namespace - * in the consumed namespacesList. These flags are used for communicating the namespaces - * (aka platform packages) whose flags in {@link DeviceConfig} were just reset - * by the RescueParty. - */ - public static void addToKnownResetNamespaces(@Nullable List<String> namespacesList) { - if (namespacesList == null) { - return; - } - for (String namespace : namespacesList) { - addToKnownResetNamespaces(namespace); - } - } - - /** - * Sets the union of {@link #RESET_PLATFORM_PACKAGE_FLAG} with - * {@link #sKnownResetNamespacesFlagCounter} in the DeviceConfig for the consumed namespace. - * This flag is used for communicating the namespace (aka platform package) whose flags - * in {@link DeviceConfig} were just reset by the RescueParty. - */ - public static void addToKnownResetNamespaces(String namespace) { - int nextFlagCounter = incrementAndRetrieveResetNamespacesFlagCounter(); - DeviceConfig.setProperty(NAMESPACE_RESCUE_PARTY, - RESET_PLATFORM_PACKAGE_FLAG + nextFlagCounter, - namespace, /*makeDefault=*/ true); - } - - /** - * Reset all namespaces in DeviceConfig with consumed resetMode. - */ - public static void resetDeviceConfig(int resetMode) { - resetDeviceConfig(resetMode, getAllKnownDeviceConfigNamespacesList()); - } - - /** - * Reset all consumed namespaces in DeviceConfig with consumed resetMode. - */ - public static void resetDeviceConfig(int resetMode, List<String> namespacesList) { - for (String namespace : namespacesList) { - DeviceConfig.resetToDefaults(resetMode, namespace); - } - addToKnownResetNamespaces(namespacesList); - } - - /** - * Resets known reset namespaces flag counter for tests only. - */ - @VisibleForTesting - public static void resetKnownResetNamespacesFlagCounterForTest() { - sKnownResetNamespacesFlagCounter = -1; - } - - /** - * Returns a list of all known DeviceConfig namespaces, except for the special {@link - * #NAMESPACE_RESCUE_PARTY} - */ - private static List<String> getAllKnownDeviceConfigNamespacesList() { - String namespacesStr = DeviceConfig.getProperty(NAMESPACE_RESCUE_PARTY, - ALL_KNOWN_NAMESPACES_FLAG); - List<String> namespacesList = toStringList(namespacesStr); - namespacesList.remove(NAMESPACE_RESCUE_PARTY); - return namespacesList; - } - - private static List<String> toStringList(String serialized) { - if (serialized == null || serialized.length() == 0) { - return new ArrayList<>(); - } - return Arrays.asList(serialized.split(DELIMITER)); - } - - private static int incrementAndRetrieveResetNamespacesFlagCounter() { - sKnownResetNamespacesFlagCounter++; - if (sKnownResetNamespacesFlagCounter == MAX_COUNTER_VALUE) { - sKnownResetNamespacesFlagCounter = 0; - } - return sKnownResetNamespacesFlagCounter; - } -} diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 03139d2e5e03..36e97755ab8a 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -2180,6 +2180,47 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } + /** + * Called when the wallpaper needs to zoom out. + * + * @param zoom from 0 to 1 (inclusive) where 1 means fully zoomed out, 0 means fully zoomed in. + * @param callingPackage package name calling this API. + * @param displayId id of the display whose zoom is updating. + */ + public void setWallpaperZoomOut(float zoom, String callingPackage, int displayId) { + if (!isWallpaperSupported(callingPackage)) { + return; + } + synchronized (mLock) { + if (!isValidDisplay(displayId)) { + throw new IllegalArgumentException("Cannot find display with id=" + displayId); + } + int userId = UserHandle.getCallingUserId(); + if (mCurrentUserId != userId) { + return; // Don't change the properties now + } + WallpaperData wallpaper = getWallpaperSafeLocked(userId, FLAG_SYSTEM); + if (zoom < 0 || zoom > 1f) { + throw new IllegalArgumentException("zoom must be between 0 and one: " + zoom); + } + + if (wallpaper.connection != null) { + final WallpaperConnection.DisplayConnector connector = wallpaper.connection + .getDisplayConnectorOrCreate(displayId); + final IWallpaperEngine engine = connector != null ? connector.mEngine : null; + if (engine != null) { + try { + engine.setZoomOut(zoom); + } catch (RemoteException e) { + if (DEBUG) { + Slog.w(TAG, "Couldn't set wallpaper zoom", e); + } + } + } + } + } + } + @Deprecated @Override public ParcelFileDescriptor getWallpaper(String callingPkg, IWallpaperManagerCallback cb, diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 2c325e56cebe..31311bd36514 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -63,7 +63,6 @@ import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_M import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; @@ -362,7 +361,6 @@ public class DisplayPolicy { private static final Rect sTmpRect = new Rect(); private static final Rect sTmpNavFrame = new Rect(); private static final Rect sTmpLastParentFrame = new Rect(); - private static final int[] sTmpTypesAndSides = new int[2]; private WindowState mTopFullscreenOpaqueWindowState; private WindowState mTopFullscreenOpaqueOrDimmingWindowState; @@ -888,6 +886,21 @@ public class DisplayPolicy { // Toasts can't be clickable attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; break; + + case TYPE_BASE_APPLICATION: + + // A non-translucent main app window isn't allowed to fit insets, as it would create + // a hole on the display! + if (attrs.isFullscreen() && win.mActivityRecord != null + && win.mActivityRecord.fillsParent() + && (win.mAttrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0 + && attrs.getFitWindowInsetsTypes() != 0) { + throw new RuntimeException("Illegal attributes: Main activity window that isn't" + + " translucent trying to fit insets: " + + attrs.getFitWindowInsetsTypes() + + " attrs=" + attrs); + } + break; } } @@ -1300,21 +1313,6 @@ public class DisplayPolicy { } } - private static void getImpliedTypesAndSidesToFit(LayoutParams attrs, int[] typesAndSides) { - typesAndSides[0] = attrs.getFitWindowInsetsTypes(); - typesAndSides[1] = attrs.getFitWindowInsetsSides(); - final boolean forceDrawsBarBackgrounds = - (attrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0 - && attrs.height == MATCH_PARENT && attrs.width == MATCH_PARENT; - if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 || forceDrawsBarBackgrounds) { - if ((attrs.privateFlags & PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND) != 0) { - typesAndSides[1] &= ~Side.BOTTOM; - } else { - typesAndSides[0] &= ~Type.systemBars(); - } - } - } - // TODO(b/118118435): remove after migration private static int getImpliedSysUiFlagsForLayout(LayoutParams attrs) { int impliedFlags = 0; @@ -1878,9 +1876,8 @@ public class DisplayPolicy { sf.set(displayFrames.mStable); if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_FULL) { - getImpliedTypesAndSidesToFit(attrs, sTmpTypesAndSides); - final @InsetsType int typesToFit = sTmpTypesAndSides[0]; - final @InsetsSide int sidesToFit = sTmpTypesAndSides[1]; + final @InsetsType int typesToFit = attrs.getFitWindowInsetsTypes(); + final @InsetsSide int sidesToFit = attrs.getFitWindowInsetsSides(); final ArraySet<Integer> types = InsetsState.toInternalType(typesToFit); final Rect dfu = displayFrames.mUnrestricted; Insets insets = Insets.of(0, 0, 0, 0); @@ -1958,7 +1955,7 @@ public class DisplayPolicy { } } else if (type == TYPE_WALLPAPER) { layoutWallpaper(displayFrames, pf, df, cf); - } else if (win == mStatusBar) { + } else if (win == mStatusBar || type == TYPE_NOTIFICATION_SHADE) { df.set(displayFrames.mUnrestricted); pf.set(displayFrames.mUnrestricted); cf.set(displayFrames.mStable); diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index d3da50060104..0b54245cd424 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2004,7 +2004,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> final ActivityStack stack = display.getStackAt(stackNdx); stack.switchUser(userId); Task task = stack.getTopMostTask(); - if (task != null) { + if (task != null && task != stack) { stack.positionChildAtTop(task); } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 81a4c682e809..e82394c5ae27 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -71,6 +71,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED; +import static android.view.WindowManagerGlobal.ADD_OKAY; import static android.view.WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY; import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED; import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID; @@ -1367,52 +1368,10 @@ public class WindowManagerService extends IWindowManager.Stub boolean addToastWindowRequiresToken = false; if (token == null) { - if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) { - ProtoLog.w(WM_ERROR, "Attempted to add application window with unknown token " - + "%s. Aborting.", attrs.token); - return WindowManagerGlobal.ADD_BAD_APP_TOKEN; - } - if (rootType == TYPE_INPUT_METHOD) { - ProtoLog.w(WM_ERROR, "Attempted to add input method window with unknown token " - + "%s. Aborting.", attrs.token); - return WindowManagerGlobal.ADD_BAD_APP_TOKEN; - } - if (rootType == TYPE_VOICE_INTERACTION) { - ProtoLog.w(WM_ERROR, - "Attempted to add voice interaction window with unknown token " - + "%s. Aborting.", attrs.token); - return WindowManagerGlobal.ADD_BAD_APP_TOKEN; - } - if (rootType == TYPE_WALLPAPER) { - ProtoLog.w(WM_ERROR, "Attempted to add wallpaper window with unknown token " - + "%s. Aborting.", attrs.token); - return WindowManagerGlobal.ADD_BAD_APP_TOKEN; - } - if (rootType == TYPE_DREAM) { - ProtoLog.w(WM_ERROR, "Attempted to add Dream window with unknown token " - + "%s. Aborting.", attrs.token); - return WindowManagerGlobal.ADD_BAD_APP_TOKEN; - } - if (rootType == TYPE_QS_DIALOG) { - ProtoLog.w(WM_ERROR, "Attempted to add QS dialog window with unknown token " - + "%s. Aborting.", attrs.token); + if (!unprivilegedAppCanCreateTokenWith(parentWindow, callingUid, type, + rootType, attrs.token, attrs.packageName)) { return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } - if (rootType == TYPE_ACCESSIBILITY_OVERLAY) { - ProtoLog.w(WM_ERROR, - "Attempted to add Accessibility overlay window with unknown token " - + "%s. Aborting.", attrs.token); - return WindowManagerGlobal.ADD_BAD_APP_TOKEN; - } - if (type == TYPE_TOAST) { - // Apps targeting SDK above N MR1 cannot arbitrary add toast windows. - if (doesAddToastWindowRequireToken(attrs.packageName, callingUid, - parentWindow)) { - ProtoLog.w(WM_ERROR, "Attempted to add a toast window with unknown token " - + "%s. Aborting.", attrs.token); - return WindowManagerGlobal.ADD_BAD_APP_TOKEN; - } - } final IBinder binder = attrs.token != null ? attrs.token : client.asBinder(); final boolean isRoundedCornerOverlay = (attrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0; @@ -1697,6 +1656,56 @@ public class WindowManagerService extends IWindowManager.Stub return res; } + private boolean unprivilegedAppCanCreateTokenWith(WindowState parentWindow, + int callingUid, int type, int rootType, IBinder tokenForLog, String packageName) { + if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) { + ProtoLog.w(WM_ERROR, "Attempted to add application window with unknown token " + + "%s. Aborting.", tokenForLog); + return false; + } + if (rootType == TYPE_INPUT_METHOD) { + ProtoLog.w(WM_ERROR, "Attempted to add input method window with unknown token " + + "%s. Aborting.", tokenForLog); + return false; + } + if (rootType == TYPE_VOICE_INTERACTION) { + ProtoLog.w(WM_ERROR, + "Attempted to add voice interaction window with unknown token " + + "%s. Aborting.", tokenForLog); + return false; + } + if (rootType == TYPE_WALLPAPER) { + ProtoLog.w(WM_ERROR, "Attempted to add wallpaper window with unknown token " + + "%s. Aborting.", tokenForLog); + return false; + } + if (rootType == TYPE_DREAM) { + ProtoLog.w(WM_ERROR, "Attempted to add Dream window with unknown token " + + "%s. Aborting.", tokenForLog); + return false; + } + if (rootType == TYPE_QS_DIALOG) { + ProtoLog.w(WM_ERROR, "Attempted to add QS dialog window with unknown token " + + "%s. Aborting.", tokenForLog); + return false; + } + if (rootType == TYPE_ACCESSIBILITY_OVERLAY) { + ProtoLog.w(WM_ERROR, + "Attempted to add Accessibility overlay window with unknown token " + + "%s. Aborting.", tokenForLog); + return false; + } + if (type == TYPE_TOAST) { + // Apps targeting SDK above N MR1 cannot arbitrary add toast windows. + if (doesAddToastWindowRequireToken(packageName, callingUid, parentWindow)) { + ProtoLog.w(WM_ERROR, "Attempted to add a toast window with unknown token " + + "%s. Aborting.", tokenForLog); + return false; + } + } + return true; + } + /** * Get existing {@link DisplayContent} or create a new one if the display is registered in * DisplayManager. @@ -2501,16 +2510,36 @@ public class WindowManagerService extends IWindowManager.Stub @Override public void addWindowToken(IBinder binder, int type, int displayId) { - if (!checkCallingPermission(MANAGE_APP_TOKENS, "addWindowToken()")) { - throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); + addWindowContextToken(binder, type, displayId, null); + } + + @Override + public int addWindowContextToken(IBinder binder, int type, int displayId, String packageName) { + final boolean callerCanManageAppTokens = + checkCallingPermission(MANAGE_APP_TOKENS, "addWindowToken()"); + if (!callerCanManageAppTokens) { + // TODO(window-context): refactor checkAddPermission to not take attrs. + LayoutParams attrs = new LayoutParams(type); + attrs.packageName = packageName; + final int res = mPolicy.checkAddPermission(attrs, new int[1]); + if (res != ADD_OKAY) { + return res; + } } synchronized (mGlobalLock) { + if (!callerCanManageAppTokens) { + if (!unprivilegedAppCanCreateTokenWith(null, Binder.getCallingUid(), type, type, + null, packageName) || packageName == null) { + throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); + } + } + final DisplayContent dc = getDisplayContentOrCreate(displayId, null /* token */); if (dc == null) { ProtoLog.w(WM_ERROR, "addWindowToken: Attempted to add token: %s" + " for non-exiting displayId=%d", binder, displayId); - return; + return WindowManagerGlobal.ADD_INVALID_DISPLAY; } WindowToken token = dc.getWindowToken(binder); @@ -2518,14 +2547,27 @@ public class WindowManagerService extends IWindowManager.Stub ProtoLog.w(WM_ERROR, "addWindowToken: Attempted to add binder token: %s" + " for already created window token: %s" + " displayId=%d", binder, token, displayId); - return; + return WindowManagerGlobal.ADD_DUPLICATE_ADD; } + // TODO(window-container): Clean up dead tokens if (type == TYPE_WALLPAPER) { - new WallpaperWindowToken(this, binder, true, dc, - true /* ownerCanManageAppTokens */); + new WallpaperWindowToken(this, binder, true, dc, callerCanManageAppTokens); } else { - new WindowToken(this, binder, type, true, dc, true /* ownerCanManageAppTokens */); + new WindowToken(this, binder, type, true, dc, callerCanManageAppTokens); + } + } + return WindowManagerGlobal.ADD_OKAY; + } + + @Override + public boolean isWindowToken(IBinder binder) { + synchronized (mGlobalLock) { + final WindowToken windowToken = mRoot.getWindowToken(binder); + if (windowToken == null) { + return false; } + // We don't allow activity tokens in WindowContext. TODO(window-context): rename method + return windowToken.asActivityRecord() == null; } } diff --git a/services/core/xsd/Android.bp b/services/core/xsd/Android.bp index 8b2cbbde2db8..b7d6424450f3 100644 --- a/services/core/xsd/Android.bp +++ b/services/core/xsd/Android.bp @@ -12,3 +12,11 @@ xsd_config { api_dir: "platform-compat-schema", package_name: "com.android.server.compat.config", } + + +xsd_config { + name: "display-device-config", + srcs: ["display-device-config/display-device-config.xsd"], + api_dir: "display-device-config/schema", + package_name: "com.android.server.display.config", +} diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd new file mode 100644 index 000000000000..5c7f30576741 --- /dev/null +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -0,0 +1,54 @@ +<?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. + --> + +<!-- This defines the format of the XML file generated by + ~ com.android.compat.annotation.ChangeIdProcessor annotation processor (from + ~ tools/platform-compat), and is parsed in com/android/server/compat/CompatConfig.java. +--> +<xs:schema version="2.0" + elementFormDefault="qualified" + xmlns:xs="http://www.w3.org/2001/XMLSchema"> + <xs:element name="displayConfiguration"> + <xs:complexType> + <xs:sequence> + <xs:element type="nitsMap" name="screenBrightnessMap"/> + </xs:sequence> + </xs:complexType> + </xs:element> + + <!-- Type definitions --> + + <xs:complexType name="nitsMap"> + <xs:sequence> + <xs:element name="point" type="point" maxOccurs="unbounded" minOccurs="2"/> + <xs:element name="highBrightnessStart" minOccurs="0" type="nonNegativeDecimal"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="point"> + <xs:sequence> + <xs:element type="nonNegativeDecimal" name="value"/> + <xs:element type="nonNegativeDecimal" name="nits"/> + </xs:sequence> + </xs:complexType> + + <xs:simpleType name="nonNegativeDecimal"> + <xs:restriction base="xs:decimal"> + <xs:minInclusive value="0.0"/> + </xs:restriction> + </xs:simpleType> +</xs:schema> diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt new file mode 100644 index 000000000000..5a9c9457b423 --- /dev/null +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -0,0 +1,33 @@ +// Signature format: 2.0 +package com.android.server.display.config { + + public class DisplayConfiguration { + ctor public DisplayConfiguration(); + method public com.android.server.display.config.NitsMap getScreenBrightnessMap(); + method public void setScreenBrightnessMap(com.android.server.display.config.NitsMap); + } + + public class NitsMap { + ctor public NitsMap(); + method public java.math.BigDecimal getHighBrightnessStart(); + method public java.util.List<com.android.server.display.config.Point> getPoint(); + method public void setHighBrightnessStart(java.math.BigDecimal); + } + + public class Point { + ctor public Point(); + method public java.math.BigDecimal getNits(); + method public java.math.BigDecimal getValue(); + method public void setNits(java.math.BigDecimal); + method public void setValue(java.math.BigDecimal); + } + + public class XmlParser { + ctor public XmlParser(); + method public static com.android.server.display.config.DisplayConfiguration read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method public static void skip(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; + } + +} + diff --git a/services/core/xsd/display-device-config/schema/last_current.txt b/services/core/xsd/display-device-config/schema/last_current.txt new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/services/core/xsd/display-device-config/schema/last_current.txt diff --git a/services/core/xsd/display-device-config/schema/last_removed.txt b/services/core/xsd/display-device-config/schema/last_removed.txt new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/services/core/xsd/display-device-config/schema/last_removed.txt diff --git a/services/core/xsd/display-device-config/schema/removed.txt b/services/core/xsd/display-device-config/schema/removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/services/core/xsd/display-device-config/schema/removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index f22b8db3c982..0171582e9d86 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -158,6 +158,7 @@ import android.content.IntentFilter; import android.content.PermissionChecker; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.CrossProfileApps; import android.content.pm.IPackageDataObserver; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; @@ -305,6 +306,7 @@ import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -313,6 +315,7 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; +import java.util.stream.Collectors; /** * Implementation of the device policy APIs. @@ -447,6 +450,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // A collection of user restrictions that are deprecated and should simply be ignored. private static final Set<String> DEPRECATED_USER_RESTRICTIONS; private static final String AB_DEVICE_KEY = "ro.build.ab_update"; + // Permissions related to location which must not be granted automatically + private static final Set<String> LOCATION_PERMISSIONS; static { SECURE_SETTINGS_WHITELIST = new ArraySet<>(); @@ -491,6 +496,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { DEPRECATED_USER_RESTRICTIONS = Sets.newHashSet( UserManager.DISALLOW_ADD_MANAGED_PROFILE, UserManager.DISALLOW_REMOVE_MANAGED_PROFILE); + + LOCATION_PERMISSIONS = Sets.newHashSet( + permission.ACCESS_FINE_LOCATION, + permission.ACCESS_BACKGROUND_LOCATION, + permission.ACCESS_COARSE_LOCATION); } /** @@ -529,11 +539,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { /** * For admin apps targeting R+, throw when the app sets password requirement * that is not taken into account at given quality. For example when quality is set - * to {@link DevicePolicyManager#PASSWORD_QUALITY_UNSPECIFIED}, it doesn't make sense to - * require certain password length. If the intent is to require a password of certain length - * having at least NUMERIC quality, the admin should first call - * {@link #setPasswordQuality(ComponentName, int, boolean)} and only then call - * {@link #setPasswordMinimumLength(ComponentName, int, boolean)}. + * to {@link android.app.admin.DevicePolicyManager#PASSWORD_QUALITY_UNSPECIFIED}, it doesn't + * make sense to require certain password length. If the intent is to require a password of + * certain length having at least NUMERIC quality, the admin should first call + * {@link android.app.admin.DevicePolicyManager#setPasswordQuality} and only then call + * {@link android.app.admin.DevicePolicyManager#setPasswordMinimumLength}. * * <p>Conversely when an admin app targeting R+ lowers password quality, those * requirements that stop making sense are reset to default values. @@ -12446,6 +12456,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { true); } + // Prevent granting location-related permissions without user consent. + if (LOCATION_PERMISSIONS.contains(permission) + && grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED + && !isUnattendedManagedKioskUnchecked()) { + callback.sendResult(null); + return; + } + if (grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) { @@ -14946,12 +14964,18 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Objects.requireNonNull(who, "ComponentName is null"); Objects.requireNonNull(packageNames, "Package names is null"); + final List<String> previousCrossProfilePackages; synchronized (getLockObject()) { final ActiveAdmin admin = getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + previousCrossProfilePackages = admin.mCrossProfilePackages; admin.mCrossProfilePackages = packageNames; saveSettingsLocked(mInjector.userHandleGetCallingUserId()); } + final CrossProfileApps crossProfileApps = mContext.getSystemService(CrossProfileApps.class); + mInjector.binderWithCleanCallingIdentity( + () -> crossProfileApps.resetInteractAcrossProfilesAppOps( + previousCrossProfilePackages, new HashSet<>(packageNames))); } @Override @@ -15031,23 +15055,24 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } - @Override - public boolean isUnattendedManagedKiosk() { - if (!mHasFeature) { - return false; - } - enforceManageUsers(); - long id = mInjector.binderClearCallingIdentity(); + private boolean isUnattendedManagedKioskUnchecked() { try { return isManagedKioskInternal() && getPowerManagerInternal().wasDeviceIdleFor(UNATTENDED_MANAGED_KIOSK_MS); } catch (RemoteException e) { throw new IllegalStateException(e); - } finally { - mInjector.binderRestoreCallingIdentity(id); } } + @Override + public boolean isUnattendedManagedKiosk() { + if (!mHasFeature) { + return false; + } + enforceManageUsers(); + return mInjector.binderWithCleanCallingIdentity(() -> isUnattendedManagedKioskUnchecked()); + } + /** * Returns whether the device is currently being used as a publicly-accessible dedicated device. * Assumes that feature checks and permission checks have already been performed, and that the diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java index ec56e1ebc8e0..62ff3a1c2126 100644 --- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java +++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java @@ -96,6 +96,7 @@ import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; +import android.util.FeatureFlagUtils; import android.util.Pair; import com.android.internal.backup.IBackupTransport; @@ -258,6 +259,9 @@ public class KeyValueBackupTaskTest { public void tearDown() throws Exception { ShadowBackupDataInput.reset(); ShadowApplicationPackageManager.reset(); + // False by default. + FeatureFlagUtils.setEnabled( + mContext, FeatureFlagUtils.BACKUP_NO_KV_DATA_CHANGE_CALLS, false); } @Test @@ -2344,6 +2348,9 @@ public class KeyValueBackupTaskTest { @Test public void testRunTask_whenNoDataToBackupOnFirstBackup_doesNotTellTransportOfBackup() throws Exception { + FeatureFlagUtils.setEnabled( + mContext, FeatureFlagUtils.BACKUP_NO_KV_DATA_CHANGE_CALLS, true); + TransportMock transportMock = setUpInitializedTransport(mTransport); mBackupManagerService.setCurrentToken(0L); when(transportMock.transport.getCurrentRestoreSet()).thenReturn(1234L); @@ -2361,6 +2368,9 @@ public class KeyValueBackupTaskTest { @Test public void testRunTask_whenBackupHasCompletedAndThenNoDataChanges_transportGetsNotified() throws Exception { + FeatureFlagUtils.setEnabled( + mContext, FeatureFlagUtils.BACKUP_NO_KV_DATA_CHANGE_CALLS, true); + TransportMock transportMock = setUpInitializedTransport(mTransport); when(transportMock.transport.getCurrentRestoreSet()).thenReturn(1234L); when(transportMock.transport.isAppEligibleForBackup( diff --git a/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java b/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java index 96ff9c1ba726..1a7b1d3f6039 100644 --- a/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java +++ b/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java @@ -18,6 +18,8 @@ package com.android.server.pm; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.OP_INTERACT_ACROSS_PROFILES; +import static android.content.Intent.FLAG_RECEIVER_FOREGROUND; +import static android.content.Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND; import static android.content.Intent.FLAG_RECEIVER_REGISTERED_ONLY; import static android.content.pm.CrossProfileApps.ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED; @@ -37,11 +39,13 @@ import android.app.AppOpsManager.Mode; import android.app.admin.DevicePolicyManagerInternal; import android.content.ContextWrapper; import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; +import android.content.pm.ResolveInfo; import android.content.pm.parsing.AndroidPackage; import android.content.pm.parsing.PackageImpl; import android.os.Process; @@ -70,6 +74,7 @@ import org.robolectric.shadow.api.Shadow; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -106,6 +111,7 @@ public class CrossProfileAppsServiceImplRoboTest { MockitoAnnotations.initMocks(this); mockCrossProfileAppInstalledAndEnabledOnEachProfile(); mockCrossProfileAppRequestsInteractAcrossProfiles(); + mockCrossProfileAppRegistersBroadcastReceiver(); mockCrossProfileAppWhitelisted(); } @@ -113,19 +119,21 @@ public class CrossProfileAppsServiceImplRoboTest { // They are enabled by default, so we simply have to ensure that a package info with an // application info is returned. final PackageInfo packageInfo = buildTestPackageInfo(); + mockCrossProfileAppInstalledOnProfile( + packageInfo, PERSONAL_PROFILE_USER_ID, PERSONAL_PROFILE_UID); + mockCrossProfileAppInstalledOnProfile(packageInfo, WORK_PROFILE_USER_ID, WORK_PROFILE_UID); + } + + private void mockCrossProfileAppInstalledOnProfile( + PackageInfo packageInfo, @UserIdInt int userId, int uid) { when(mPackageManagerInternal.getPackageInfo( eq(CROSS_PROFILE_APP_PACKAGE_NAME), /* flags= */ anyInt(), /* filterCallingUid= */ anyInt(), - eq(PERSONAL_PROFILE_USER_ID))) + eq(userId))) .thenReturn(packageInfo); - when(mPackageManagerInternal.getPackageInfo( - eq(CROSS_PROFILE_APP_PACKAGE_NAME), - /* flags= */ anyInt(), - /* filterCallingUid= */ anyInt(), - eq(WORK_PROFILE_USER_ID))) - .thenReturn(packageInfo); - mockCrossProfileAndroidPackage(PackageImpl.forParsing(CROSS_PROFILE_APP_PACKAGE_NAME)); + when(mPackageManagerInternal.getPackage(uid)) + .thenReturn(PackageImpl.forParsing(CROSS_PROFILE_APP_PACKAGE_NAME)); } private PackageInfo buildTestPackageInfo() { @@ -140,6 +148,31 @@ public class CrossProfileAppsServiceImplRoboTest { .thenReturn(new String[] {CROSS_PROFILE_APP_PACKAGE_NAME}); } + private void mockCrossProfileAppRegistersBroadcastReceiver() { + final ShadowApplicationPackageManager shadowApplicationPackageManager = + Shadow.extract(mPackageManager); + final Intent baseIntent = + new Intent(ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED) + .setPackage(CROSS_PROFILE_APP_PACKAGE_NAME); + final Intent manifestIntent = + new Intent(baseIntent) + .setFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND + | Intent.FLAG_RECEIVER_FOREGROUND); + final Intent registeredIntent = + new Intent(baseIntent).setFlags(FLAG_RECEIVER_REGISTERED_ONLY); + final List<ResolveInfo> resolveInfos = Lists.newArrayList(buildTestResolveInfo()); + shadowApplicationPackageManager.setResolveInfosForIntent(manifestIntent, resolveInfos); + shadowApplicationPackageManager.setResolveInfosForIntent(registeredIntent, resolveInfos); + } + + private ResolveInfo buildTestResolveInfo() { + final ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.activityInfo = new ActivityInfo(); + resolveInfo.activityInfo.packageName = CROSS_PROFILE_APP_PACKAGE_NAME; + resolveInfo.activityInfo.name = CROSS_PROFILE_APP_PACKAGE_NAME + ".Receiver"; + return resolveInfo; + } + private void mockCrossProfileAppWhitelisted() { when(mDevicePolicyManagerInternal.getAllCrossProfilePackages()) .thenReturn(Lists.newArrayList(CROSS_PROFILE_APP_PACKAGE_NAME)); @@ -191,16 +224,6 @@ public class CrossProfileAppsServiceImplRoboTest { } @Test - public void setInteractAcrossProfilesAppOp_missingManageAppOpsModes_throwsSecurityException() { - denyPermissions(Manifest.permission.MANAGE_APP_OPS_MODES); - try { - mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp( - CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED); - fail(); - } catch (SecurityException expected) {} - } - - @Test public void setInteractAcrossProfilesAppOp_setsAppOp() { mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp( CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED); @@ -294,6 +317,54 @@ public class CrossProfileAppsServiceImplRoboTest { assertThat(receivedManifestCanInteractAcrossProfilesChangedBroadcast()).isTrue(); } + @Test + public void canConfigureInteractAcrossProfiles_packageNotInstalledInProfile_returnsFalse() { + mockUninstallCrossProfileAppFromWorkProfile(); + assertThat(mCrossProfileAppsServiceImpl + .canConfigureInteractAcrossProfiles(CROSS_PROFILE_APP_PACKAGE_NAME)) + .isFalse(); + } + + private void mockUninstallCrossProfileAppFromWorkProfile() { + when(mPackageManagerInternal.getPackageInfo( + eq(CROSS_PROFILE_APP_PACKAGE_NAME), + /* flags= */ anyInt(), + /* filterCallingUid= */ anyInt(), + eq(WORK_PROFILE_USER_ID))) + .thenReturn(null); + when(mPackageManagerInternal.getPackage(WORK_PROFILE_UID)).thenReturn(null); + } + + @Test + public void canConfigureInteractAcrossProfiles_packageDoesNotRequestInteractAcrossProfiles_returnsFalse() + throws Exception { + mockCrossProfileAppDoesNotRequestInteractAcrossProfiles(); + assertThat(mCrossProfileAppsServiceImpl + .canConfigureInteractAcrossProfiles(CROSS_PROFILE_APP_PACKAGE_NAME)) + .isFalse(); + } + + private void mockCrossProfileAppDoesNotRequestInteractAcrossProfiles() throws Exception { + final String permissionName = Manifest.permission.INTERACT_ACROSS_PROFILES; + when(mIPackageManager.getAppOpPermissionPackages(permissionName)) + .thenReturn(new String[] {}); + } + + @Test + public void canConfigureInteractAcrossProfiles_packageNotWhitelisted_returnsFalse() { + mockCrossProfileAppNotWhitelisted(); + assertThat(mCrossProfileAppsServiceImpl + .canConfigureInteractAcrossProfiles(CROSS_PROFILE_APP_PACKAGE_NAME)) + .isFalse(); + } + + @Test + public void canConfigureInteractAcrossProfiles_returnsTrue() { + assertThat(mCrossProfileAppsServiceImpl + .canConfigureInteractAcrossProfiles(CROSS_PROFILE_APP_PACKAGE_NAME)) + .isTrue(); + } + private void explicitlySetInteractAcrossProfilesAppOp(@Mode int mode) { explicitlySetInteractAcrossProfilesAppOp(PERSONAL_PROFILE_UID, mode); } @@ -311,7 +382,6 @@ public class CrossProfileAppsServiceImplRoboTest { shadowOf(mContext).denyPermissions(Process.myPid(), CALLING_UID, permissions); } - private @Mode int getCrossProfileAppOp() { return getCrossProfileAppOp(PERSONAL_PROFILE_UID); } @@ -365,10 +435,12 @@ public class CrossProfileAppsServiceImplRoboTest { } private boolean isBroadcastManifestCanInteractAcrossProfilesChanged(Intent intent) { - // The manifest check is negative since the FLAG_RECEIVER_REGISTERED_ONLY flag means that - // manifest receivers can NOT receive the broadcast. return isBroadcastCanInteractAcrossProfilesChanged(intent) - && (intent.getFlags() & FLAG_RECEIVER_REGISTERED_ONLY) == 0; + && (intent.getFlags() & FLAG_RECEIVER_REGISTERED_ONLY) == 0 + && (intent.getFlags() & FLAG_RECEIVER_INCLUDE_BACKGROUND) != 0 + && (intent.getFlags() & FLAG_RECEIVER_FOREGROUND) != 0 + && intent.getComponent() != null + && intent.getComponent().getPackageName().equals(CROSS_PROFILE_APP_PACKAGE_NAME); } private void declareCrossProfileAttributeOnCrossProfileApp(boolean value) { diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java b/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java index 1443eabf07d5..aea36e555ad7 100644 --- a/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java +++ b/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java @@ -19,7 +19,10 @@ package com.android.server.testing.shadows; import static android.content.pm.PackageManager.NameNotFoundException; import android.app.ApplicationPackageManager; +import android.content.Intent; import android.content.pm.PackageInfo; +import android.content.pm.ResolveInfo; +import android.os.UserHandle; import android.util.ArrayMap; import org.robolectric.annotation.Implements; @@ -100,6 +103,13 @@ public class ShadowApplicationPackageManager return sPackageUids.get(packageName); } + @Override + protected List<ResolveInfo> queryBroadcastReceiversAsUser( + Intent intent, int flags, UserHandle userHandle) { + // Currently does not handle multi-user. + return queryBroadcastReceivers(intent, flags); + } + /** Clear package state. */ @Resetter public static void reset() { diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml index b3b5af0796ab..0e24b0314b2d 100644 --- a/services/tests/mockingservicestests/AndroidManifest.xml +++ b/services/tests/mockingservicestests/AndroidManifest.xml @@ -21,6 +21,7 @@ <uses-permission android:name="android.permission.HARDWARE_TEST"/> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> <uses-permission android:name="android.permission.MANAGE_APPOPS"/> + <uses-permission android:name="android.permission.MONITOR_DEVICE_CONFIG_ACCESS"/> <application android:testOnly="true" android:debuggable="true"> diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java index c3602f8db9bc..2080fdf2e40d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java @@ -30,11 +30,14 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.times; import android.content.ContentResolver; import android.content.Context; import android.content.pm.VersionedPackage; +import android.os.Bundle; import android.os.RecoverySystem; +import android.os.RemoteCallback; import android.os.SystemProperties; import android.os.UserHandle; import android.provider.DeviceConfig; @@ -44,18 +47,21 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.server.PackageWatchdog.PackageHealthObserverImpact; import com.android.server.RescueParty.RescuePartyObserver; import com.android.server.am.SettingsToPropertiesMapper; -import com.android.server.utils.FlagNamespaceUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.Answers; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoSession; import org.mockito.quality.Strictness; import org.mockito.stubbing.Answer; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; /** * Test RescueParty. @@ -69,16 +75,25 @@ public class RescuePartyTest { private static VersionedPackage sFailingPackage = new VersionedPackage("com.package.name", 1); private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue"; + private static final String CALLING_PACKAGE1 = "com.package.name1"; + private static final String CALLING_PACKAGE2 = "com.package.name2"; + private static final String NAMESPACE1 = "namespace1"; + private static final String NAMESPACE2 = "namespace2"; private MockitoSession mSession; + private HashMap<String, String> mSystemSettingsMap; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Context mMockContext; - + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private PackageWatchdog mMockPackageWatchdog; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private ContentResolver mMockContentResolver; - private HashMap<String, String> mSystemSettingsMap; + @Captor + private ArgumentCaptor<RemoteCallback> mMonitorCallbackCaptor; + @Captor + private ArgumentCaptor<List<String>> mPackageListCaptor; @Before public void setUp() throws Exception { @@ -90,14 +105,17 @@ public class RescuePartyTest { .spyStatic(SystemProperties.class) .spyStatic(Settings.Global.class) .spyStatic(Settings.Secure.class) + .spyStatic(Settings.Config.class) .spyStatic(SettingsToPropertiesMapper.class) .spyStatic(RecoverySystem.class) .spyStatic(RescueParty.class) + .spyStatic(PackageWatchdog.class) .startMocking(); mSystemSettingsMap = new HashMap<>(); when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver); - + // Reset observer instance to get new mock context on every run + RescuePartyObserver.reset(); // Mock SystemProperties setter and various getters doAnswer((Answer<Void>) invocationOnMock -> { @@ -143,9 +161,11 @@ public class RescuePartyTest { doAnswer((Answer<Void>) invocationOnMock -> null) .when(() -> DeviceConfig.resetToDefaults(anyInt(), anyString())); + // Mock PackageWatchdog + doAnswer((Answer<PackageWatchdog>) invocationOnMock -> mMockPackageWatchdog) + .when(() -> PackageWatchdog.getInstance(mMockContext)); doReturn(CURRENT_NETWORK_TIME_MILLIS).when(() -> RescueParty.getElapsedRealtime()); - FlagNamespaceUtils.resetKnownResetNamespacesFlagCounterForTest(); SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(RescueParty.LEVEL_NONE)); @@ -162,19 +182,19 @@ public class RescuePartyTest { public void testBootLoopDetectionWithExecutionForAllRescueLevels() { noteBoot(); - verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS); + verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, /*resetNamespaces=*/ null); assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS, SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); noteBoot(); - verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES); + verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, /*resetNamespaces=*/ null); assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES, SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); noteBoot(); - verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS); + verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, /*resetNamespaces=*/ null); assertEquals(RescueParty.LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS, SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); @@ -189,19 +209,19 @@ public class RescuePartyTest { public void testPersistentAppCrashDetectionWithExecutionForAllRescueLevels() { notePersistentAppCrash(); - verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS); + verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, /*resetNamespaces=*/ null); assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS, SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); notePersistentAppCrash(); - verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES); + verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, /*resetNamespaces=*/ null); assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES, SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); notePersistentAppCrash(); - verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS); + verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, /*resetNamespaces=*/ null); assertEquals(RescueParty.LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS, SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); @@ -213,6 +233,54 @@ public class RescuePartyTest { } @Test + public void testNonPersistentAppCrashDetectionWithScopedResets() { + RescueParty.onSettingsProviderPublished(mMockContext); + verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver), + mMonitorCallbackCaptor.capture())); + + // Record DeviceConfig accesses + RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext); + RemoteCallback monitorCallback = mMonitorCallbackCaptor.getValue(); + monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE1)); + monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE2)); + monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE2)); + // Fake DeviceConfig value changes + monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE1)); + verify(mMockPackageWatchdog).startObservingHealth(observer, + Arrays.asList(CALLING_PACKAGE1), RescueParty.DEFAULT_OBSERVING_DURATION_MS); + monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE2)); + verify(mMockPackageWatchdog, times(2)).startObservingHealth(eq(observer), + mPackageListCaptor.capture(), + eq(RescueParty.DEFAULT_OBSERVING_DURATION_MS)); + assertTrue(mPackageListCaptor.getValue().containsAll( + Arrays.asList(CALLING_PACKAGE1, CALLING_PACKAGE2))); + // Perform and verify scoped resets + final String[] expectedResetNamespaces = new String[]{NAMESPACE1, NAMESPACE2}; + observer.execute(new VersionedPackage( + CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH); + verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, expectedResetNamespaces); + assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS, + SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); + + observer.execute(new VersionedPackage( + CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING); + verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, expectedResetNamespaces); + assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES, + SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); + + observer.execute(new VersionedPackage( + CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING); + verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, /*resetNamespaces=*/null); + assertEquals(RescueParty.LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS, + SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); + + observer.execute(new VersionedPackage( + CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH); + verify(() -> RecoverySystem.rebootPromptAndWipeUserData(mMockContext, RescueParty.TAG)); + assertTrue(RescueParty.isAttemptingFactoryReset()); + } + + @Test public void testIsAttemptingFactoryReset() { for (int i = 0; i < LEVEL_FACTORY_RESET; i++) { noteBoot(); @@ -227,7 +295,7 @@ public class RescuePartyTest { RescueParty.onSettingsProviderPublished(mMockContext); - verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS); + verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, /*resetNamespaces=*/ null); assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS, SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); } @@ -244,15 +312,6 @@ public class RescuePartyTest { FAKE_NATIVE_NAMESPACE1)); verify(() -> DeviceConfig.resetToDefaults(Settings.RESET_MODE_TRUSTED_DEFAULTS, FAKE_NATIVE_NAMESPACE2)); - - ExtendedMockito.verify( - () -> DeviceConfig.setProperty(FlagNamespaceUtils.NAMESPACE_RESCUE_PARTY, - FlagNamespaceUtils.RESET_PLATFORM_PACKAGE_FLAG + 0, - FAKE_NATIVE_NAMESPACE1, /*makeDefault=*/true)); - ExtendedMockito.verify( - () -> DeviceConfig.setProperty(FlagNamespaceUtils.NAMESPACE_RESCUE_PARTY, - FlagNamespaceUtils.RESET_PLATFORM_PACKAGE_FLAG + 1, - FAKE_NATIVE_NAMESPACE2, /*makeDefault=*/true)); } @Test @@ -326,11 +385,19 @@ public class RescuePartyTest { RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS); } - private void verifySettingsResets(int resetMode) { + private void verifySettingsResets(int resetMode, String[] resetNamespaces) { verify(() -> Settings.Global.resetToDefaultsAsUser(mMockContentResolver, null, resetMode, UserHandle.USER_SYSTEM)); verify(() -> Settings.Secure.resetToDefaultsAsUser(eq(mMockContentResolver), isNull(), eq(resetMode), anyInt())); + // Verify DeviceConfig resets + if (resetNamespaces == null) { + verify(() -> DeviceConfig.resetToDefaults(resetMode, /*namespace=*/ null)); + } else { + for (String namespace : resetNamespaces) { + verify(() -> DeviceConfig.resetToDefaults(resetMode, namespace)); + } + } } private void noteBoot() { @@ -339,6 +406,22 @@ public class RescuePartyTest { private void notePersistentAppCrash() { RescuePartyObserver.getInstance(mMockContext).execute(new VersionedPackage( - "com.package.name", 1), PackageWatchdog.FAILURE_REASON_UNKNOWN); + "com.package.name", 1), PackageWatchdog.FAILURE_REASON_APP_CRASH); + } + + private Bundle getConfigAccessBundle(String callingPackage, String namespace) { + Bundle result = new Bundle(); + result.putString(Settings.EXTRA_MONITOR_CALLBACK_TYPE, Settings.EXTRA_ACCESS_CALLBACK); + result.putString(Settings.EXTRA_CALLING_PACKAGE, callingPackage); + result.putString(Settings.EXTRA_NAMESPACE, namespace); + return result; + } + + private Bundle getConfigNamespaceUpdateBundle(String updatedNamespace) { + Bundle result = new Bundle(); + result.putString(Settings.EXTRA_MONITOR_CALLBACK_TYPE, + Settings.EXTRA_NAMESPACE_UPDATED_CALLBACK); + result.putString(Settings.EXTRA_NAMESPACE, updatedNamespace); + return result; } } diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 710e8dfe6aa2..d2ddff3627b9 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -74,6 +74,8 @@ <uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" /> <uses-permission android:name="android.permission.DUMP" /> + <uses-permission android:name="android.permission.READ_DREAM_STATE"/> + <uses-permission android:name="android.permission.WRITE_DREAM_STATE"/> <!-- Uses API introduced in O (26) --> <uses-sdk android:minSdkVersion="1" diff --git a/services/tests/servicestests/src/com/android/server/appsearch/impl/FakeIcingTest.java b/services/tests/servicestests/src/com/android/server/appsearch/impl/FakeIcingTest.java index 6f2de7f50379..07b655652fac 100644 --- a/services/tests/servicestests/src/com/android/server/appsearch/impl/FakeIcingTest.java +++ b/services/tests/servicestests/src/com/android/server/appsearch/impl/FakeIcingTest.java @@ -22,6 +22,7 @@ import androidx.test.runner.AndroidJUnit4; import com.google.android.icing.proto.DocumentProto; import com.google.android.icing.proto.PropertyProto; import com.google.android.icing.proto.SearchResultProto; +import com.google.android.icing.proto.StatusProto; import org.junit.Test; import org.junit.runner.RunWith; @@ -117,6 +118,7 @@ public class FakeIcingTest { private static List<String> queryGetUris(FakeIcing icing, String term) { List<String> uris = new ArrayList<>(); SearchResultProto results = icing.query(term); + assertThat(results.getStatus().getCode()).isEqualTo(StatusProto.Code.OK); for (SearchResultProto.ResultProto result : results.getResultsList()) { uris.add(result.getDocument().getUri()); } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java index 6cf6b67430ae..12228c19ca00 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java @@ -221,6 +221,8 @@ public class DpmMockContext extends MockContext { return mMockSystemServices.telephonyManager; case Context.APP_OPS_SERVICE: return mMockSystemServices.appOpsManager; + case Context.CROSS_PROFILE_APPS_SERVICE: + return mMockSystemServices.crossProfileApps; } throw new UnsupportedOperationException(); } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java index b9fb1aab65ba..37d40811571f 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java @@ -42,6 +42,7 @@ import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.CrossProfileApps; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; @@ -120,6 +121,7 @@ public class MockSystemServices { public final TimeDetector timeDetector; public final TimeZoneDetector timeZoneDetector; public final KeyChain.KeyChainConnection keyChainConnection; + public final CrossProfileApps crossProfileApps; public final PersistentDataBlockManagerInternal persistentDataBlockManagerInternal; public final AppOpsManager appOpsManager; /** Note this is a partial mock, not a real mock. */ @@ -165,6 +167,7 @@ public class MockSystemServices { timeDetector = mock(TimeDetector.class); timeZoneDetector = mock(TimeZoneDetector.class); keyChainConnection = mock(KeyChain.KeyChainConnection.class, RETURNS_DEEP_STUBS); + crossProfileApps = mock(CrossProfileApps.class); persistentDataBlockManagerInternal = mock(PersistentDataBlockManagerInternal.class); appOpsManager = mock(AppOpsManager.class); diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java index 604efc4949fe..a6af9a99788f 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java @@ -92,7 +92,7 @@ public class AppIntegrityManagerServiceImplTest { private static final String PACKAGE_NAME = "com.test.app"; private static final int VERSION_CODE = 100; - private static final String INSTALLER = TEST_FRAMEWORK_PACKAGE; + private static final String INSTALLER = "com.long.random.test.installer.name"; // These are obtained by running the test and checking logcat. private static final String APP_CERT = "301AA3CB081134501C45F1422ABC66C24224FD5DED5FDC8F17E697176FD866AA"; @@ -100,7 +100,7 @@ public class AppIntegrityManagerServiceImplTest { "301AA3CB081134501C45F1422ABC66C24224FD5DED5FDC8F17E697176FD866AA"; // We use SHA256 for package names longer than 32 characters. private static final String INSTALLER_SHA256 = - "786933C28839603EB48C50B2A688DC6BE52C833627CB2731FF8466A2AE9F94CD"; + "30F41A7CBF96EE736A54DD6DF759B50ED3CC126ABCEF694E167C324F5976C227"; private static final String PLAY_STORE_PKG = "com.android.vending"; private static final String ADB_INSTALLER = "adb"; @@ -140,7 +140,7 @@ public class AppIntegrityManagerServiceImplTest { // setup mocks to prevent NPE when(mMockContext.getPackageManager()).thenReturn(mSpyPackageManager); when(mMockContext.getResources()).thenReturn(mMockResources); - when(mMockResources.getStringArray(anyInt())).thenReturn(new String[] {}); + when(mMockResources.getStringArray(anyInt())).thenReturn(new String[]{}); when(mIntegrityFileManager.initialized()).thenReturn(true); } @@ -267,6 +267,8 @@ public class AppIntegrityManagerServiceImplTest { @Test public void broadcastReceiverRegistration() throws Exception { + whitelistUsAsRuleProvider(); + makeUsSystemApp(); ArgumentCaptor<IntentFilter> intentFilterCaptor = ArgumentCaptor.forClass(IntentFilter.class); @@ -281,6 +283,8 @@ public class AppIntegrityManagerServiceImplTest { @Test public void handleBroadcast_correctArgs() throws Exception { + whitelistUsAsRuleProvider(); + makeUsSystemApp(); ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class); verify(mMockContext) @@ -314,6 +318,8 @@ public class AppIntegrityManagerServiceImplTest { @Test public void handleBroadcast_allow() throws Exception { + whitelistUsAsRuleProvider(); + makeUsSystemApp(); ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class); verify(mMockContext) @@ -331,6 +337,8 @@ public class AppIntegrityManagerServiceImplTest { @Test public void handleBroadcast_reject() throws Exception { + whitelistUsAsRuleProvider(); + makeUsSystemApp(); ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class); verify(mMockContext) @@ -354,6 +362,8 @@ public class AppIntegrityManagerServiceImplTest { @Test public void handleBroadcast_notInitialized() throws Exception { + whitelistUsAsRuleProvider(); + makeUsSystemApp(); when(mIntegrityFileManager.initialized()).thenReturn(false); ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class); @@ -371,10 +381,30 @@ public class AppIntegrityManagerServiceImplTest { verify(mSpyPackageManager, never()).getPackageArchiveInfo(any(), anyInt()); } + @Test + public void verifierAsInstaller_skipIntegrityVerification() throws Exception { + whitelistUsAsRuleProvider(); + makeUsSystemApp(); + ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = + ArgumentCaptor.forClass(BroadcastReceiver.class); + verify(mMockContext) + .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any()); + Intent intent = makeVerificationIntent(TEST_FRAMEWORK_PACKAGE); + when(mRuleEvaluationEngine.evaluate(any(), any())).thenReturn( + IntegrityCheckResult.deny(/* rule= */ null)); + + broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent); + runJobInHandler(); + + verify(mPackageManagerInternal) + .setIntegrityVerificationResult( + 1, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW); + } + private void whitelistUsAsRuleProvider() { Resources mockResources = mock(Resources.class); when(mockResources.getStringArray(R.array.config_integrityRuleProviderPackages)) - .thenReturn(new String[] {TEST_FRAMEWORK_PACKAGE}); + .thenReturn(new String[]{TEST_FRAMEWORK_PACKAGE}); when(mMockContext.getResources()).thenReturn(mockResources); } @@ -395,15 +425,28 @@ public class AppIntegrityManagerServiceImplTest { } private Intent makeVerificationIntent() throws Exception { + PackageInfo packageInfo = + mRealContext.getPackageManager().getPackageInfo(TEST_FRAMEWORK_PACKAGE, + PackageManager.GET_SIGNATURES); + doReturn(packageInfo) + .when(mSpyPackageManager) + .getPackageInfo(eq(INSTALLER), anyInt()); + doReturn(1) + .when(mSpyPackageManager) + .getPackageUid(eq(INSTALLER), anyInt()); + return makeVerificationIntent(INSTALLER); + } + + private Intent makeVerificationIntent(String installer) throws Exception { Intent intent = new Intent(); intent.setDataAndType(Uri.fromFile(mTestApk), PACKAGE_MIME_TYPE); intent.setAction(Intent.ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION); intent.putExtra(EXTRA_VERIFICATION_ID, 1); intent.putExtra(Intent.EXTRA_PACKAGE_NAME, PACKAGE_NAME); - intent.putExtra(EXTRA_VERIFICATION_INSTALLER_PACKAGE, INSTALLER); + intent.putExtra(EXTRA_VERIFICATION_INSTALLER_PACKAGE, installer); intent.putExtra( EXTRA_VERIFICATION_INSTALLER_UID, - mRealContext.getPackageManager().getPackageUid(INSTALLER, /* flags= */ 0)); + mMockContext.getPackageManager().getPackageUid(installer, /* flags= */ 0)); intent.putExtra(Intent.EXTRA_VERSION_CODE, VERSION_CODE); return intent; } 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 355cadaa1de8..a1baf0e9ce05 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -138,6 +138,7 @@ import android.util.Range; import android.util.RecurrenceRule; import androidx.test.InstrumentationRegistry; +import androidx.test.filters.FlakyTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.util.test.BroadcastInterceptingContext; @@ -1055,6 +1056,7 @@ public class NetworkPolicyManagerServiceTest { computeLastCycleBoundary(parseTime("2013-01-14T15:11:00.000-08:00"), policy)); } + @FlakyTest @Test public void testNetworkPolicyAppliedCycleLastMonth() throws Exception { NetworkState[] state = null; 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 cb9d816509b9..7f66f3c49185 100644 --- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java @@ -51,7 +51,6 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.Collections; -import java.util.List; import java.util.Map; import java.util.Set; @@ -511,6 +510,62 @@ public class AppsFilterTest { overlaySetting, 0)); } + @Test + public void testInitiatingApp_DoesntFilter() { + final AppsFilter appsFilter = + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + appsFilter.onSystemReady(); + + PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"), + DUMMY_TARGET_UID); + PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"), + DUMMY_CALLING_UID, withInstallSource(target.name, null, null, false)); + + assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); + } + + @Test + public void testUninstalledInitiatingApp_Filters() { + final AppsFilter appsFilter = + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + appsFilter.onSystemReady(); + + PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"), + DUMMY_TARGET_UID); + PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"), + DUMMY_CALLING_UID, withInstallSource(target.name, null, null, true)); + + assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); + } + + @Test + public void testOriginatingApp_Filters() { + final AppsFilter appsFilter = + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + appsFilter.onSystemReady(); + + PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"), + DUMMY_TARGET_UID); + PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"), + DUMMY_CALLING_UID, withInstallSource(null, target.name, null, false)); + + assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); + } + + @Test + public void testInstallingApp_DoesntFilter() { + final AppsFilter appsFilter = + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + appsFilter.onSystemReady(); + + PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"), + DUMMY_TARGET_UID); + PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"), + DUMMY_CALLING_UID, withInstallSource(null, null, target.name, false)); + + assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); + } + private interface WithSettingBuilder { PackageSettingBuilder withBuilder(PackageSettingBuilder builder); } @@ -538,5 +593,13 @@ public class AppsFilterTest { return setting; } + private WithSettingBuilder withInstallSource(String initiatingPackageName, + String originatingPackageName, String installerPackageName, + boolean isInitiatingPackageUninstalled) { + final InstallSource installSource = InstallSource.create(initiatingPackageName, + originatingPackageName, installerPackageName, + /* isOrphaned= */ false, isInitiatingPackageUninstalled); + return setting -> setting.setInstallSource(installSource); + } } diff --git a/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java b/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java index 3852b9fec001..6a9ef8a2b7bd 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java @@ -15,6 +15,9 @@ */ package com.android.server.pm; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; + import android.content.Context; import android.content.pm.ModuleInfo; import android.content.pm.PackageManager; @@ -22,11 +25,25 @@ import android.test.InstrumentationTestCase; import com.android.frameworks.servicestests.R; +import org.mockito.Mock; + import java.util.Collections; import java.util.List; public class ModuleInfoProviderTest extends InstrumentationTestCase { + + @Mock private ApexManager mApexManager; + + public void setUp() { + initMocks(this); + } + public void testSuccessfulParse() { + when(mApexManager.getApexModuleNameForPackageName("com.android.module1")) + .thenReturn("com.module1.apex"); + when(mApexManager.getApexModuleNameForPackageName("com.android.module2")) + .thenReturn("com.module2.apex"); + ModuleInfoProvider provider = getProvider(R.xml.well_formed_metadata); List<ModuleInfo> mi = provider.getInstalledModules(PackageManager.MATCH_ALL); @@ -40,11 +57,13 @@ public class ModuleInfoProviderTest extends InstrumentationTestCase { ModuleInfo mi1 = provider.getModuleInfo("com.android.module1", 0); assertEquals("com.android.module1", mi1.getPackageName()); assertEquals("module_1_name", mi1.getName()); + assertEquals("com.module1.apex", mi1.getApexModuleName()); assertEquals(false, mi1.isHidden()); ModuleInfo mi2 = provider.getModuleInfo("com.android.module2", 0); assertEquals("com.android.module2", mi2.getPackageName()); assertEquals("module_2_name", mi2.getName()); + assertEquals("com.module2.apex", mi2.getApexModuleName()); assertEquals(true, mi2.isHidden()); } @@ -75,6 +94,7 @@ public class ModuleInfoProviderTest extends InstrumentationTestCase { */ private ModuleInfoProvider getProvider(int resourceId) { final Context ctx = getInstrumentation().getContext(); - return new ModuleInfoProvider(ctx.getResources().getXml(resourceId), ctx.getResources()); + return new ModuleInfoProvider( + ctx.getResources().getXml(resourceId), ctx.getResources(), mApexManager); } } diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java index 15327b6e5463..a8674a8f8be4 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java @@ -309,6 +309,7 @@ public class PackageInstallerSessionTest { actual.getStagedSessionErrorMessage()); assertEquals(expected.isPrepared(), actual.isPrepared()); assertEquals(expected.isCommitted(), actual.isCommitted()); + assertEquals(expected.createdMillis, actual.createdMillis); assertEquals(expected.isSealed(), actual.isSealed()); assertEquals(expected.isMultiPackage(), actual.isMultiPackage()); assertEquals(expected.hasParentSessionId(), actual.hasParentSessionId()); diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java index 2473997a61c9..84414947056f 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java @@ -41,6 +41,7 @@ public class PackageSettingBuilder { private SparseArray<PackageUserState> mUserStates = new SparseArray<>(); private AndroidPackage mPkg; private int mAppId; + private InstallSource mInstallSource; public PackageSettingBuilder setPackage(AndroidPackage pkg) { this.mPkg = pkg; @@ -137,6 +138,11 @@ public class PackageSettingBuilder { return this; } + public PackageSettingBuilder setInstallSource(InstallSource installSource) { + mInstallSource = installSource; + return this; + } + public PackageSetting build() { final PackageSetting packageSetting = new PackageSetting(mName, mRealName, new File(mCodePath), new File(mResourcePath), @@ -146,6 +152,9 @@ public class PackageSettingBuilder { packageSetting.pkg = mPkg; packageSetting.appId = mAppId; packageSetting.volumeUuid = this.mVolumeUuid; + if (mInstallSource != null) { + packageSetting.installSource = mInstallSource; + } for (int i = 0; i < mUserStates.size(); i++) { packageSetting.setUserState(mUserStates.keyAt(i), mUserStates.valueAt(i)); } diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java index 25cef56cd21e..6eef41aafc98 100644 --- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java @@ -806,4 +806,173 @@ public class PowerManagerServiceTest { assertThat(mService.getDesiredScreenPolicyLocked()).isEqualTo( DisplayPowerRequest.POLICY_BRIGHT); } + + @Test + public void testIsAmbientDisplayAvailable_available() throws Exception { + createService(); + when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(true); + + assertThat(mService.getBinderServiceInstance().isAmbientDisplayAvailable()).isTrue(); + } + + @Test + public void testIsAmbientDisplayAvailable_unavailable() throws Exception { + createService(); + when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(false); + + assertThat(mService.getBinderServiceInstance().isAmbientDisplayAvailable()).isFalse(); + } + + @Test + public void testSuppressAmbientDisplay_suppressed() throws Exception { + createService(); + mService.getBinderServiceInstance().suppressAmbientDisplay("test", true); + + assertThat(Settings.Secure.getInt(mContextSpy.getContentResolver(), + Settings.Secure.SUPPRESS_DOZE)).isEqualTo(1); + } + + @Test + public void testSuppressAmbientDisplay_multipleCallers_suppressed() throws Exception { + createService(); + mService.getBinderServiceInstance().suppressAmbientDisplay("test1", true); + mService.getBinderServiceInstance().suppressAmbientDisplay("test2", false); + + assertThat(Settings.Secure.getInt(mContextSpy.getContentResolver(), + Settings.Secure.SUPPRESS_DOZE)).isEqualTo(1); + } + + @Test + public void testSuppressAmbientDisplay_suppressTwice_suppressed() throws Exception { + createService(); + mService.getBinderServiceInstance().suppressAmbientDisplay("test", true); + mService.getBinderServiceInstance().suppressAmbientDisplay("test", true); + + assertThat(Settings.Secure.getInt(mContextSpy.getContentResolver(), + Settings.Secure.SUPPRESS_DOZE)).isEqualTo(1); + } + + @Test + public void testSuppressAmbientDisplay_suppressTwiceThenUnsuppress_notSuppressed() + throws Exception { + createService(); + mService.getBinderServiceInstance().suppressAmbientDisplay("test", true); + mService.getBinderServiceInstance().suppressAmbientDisplay("test", true); + mService.getBinderServiceInstance().suppressAmbientDisplay("test", false); + + assertThat(Settings.Secure.getInt(mContextSpy.getContentResolver(), + Settings.Secure.SUPPRESS_DOZE)).isEqualTo(0); + } + + @Test + public void testSuppressAmbientDisplay_notSuppressed() throws Exception { + createService(); + mService.getBinderServiceInstance().suppressAmbientDisplay("test", false); + + assertThat(Settings.Secure.getInt(mContextSpy.getContentResolver(), + Settings.Secure.SUPPRESS_DOZE)).isEqualTo(0); + } + + @Test + public void testSuppressAmbientDisplay_unsuppressTwice_notSuppressed() throws Exception { + createService(); + mService.getBinderServiceInstance().suppressAmbientDisplay("test", false); + mService.getBinderServiceInstance().suppressAmbientDisplay("test", false); + + assertThat(Settings.Secure.getInt(mContextSpy.getContentResolver(), + Settings.Secure.SUPPRESS_DOZE)).isEqualTo(0); + } + + @Test + public void testIsAmbientDisplaySuppressed_default_notSuppressed() throws Exception { + createService(); + + assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressed()).isFalse(); + } + + @Test + public void testIsAmbientDisplaySuppressed_suppressed() throws Exception { + createService(); + mService.getBinderServiceInstance().suppressAmbientDisplay("test", true); + + assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressed()).isTrue(); + } + + @Test + public void testIsAmbientDisplaySuppressed_notSuppressed() throws Exception { + createService(); + mService.getBinderServiceInstance().suppressAmbientDisplay("test", false); + + assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressed()).isFalse(); + } + + @Test + public void testIsAmbientDisplaySuppressed_multipleTokens_suppressed() throws Exception { + createService(); + mService.getBinderServiceInstance().suppressAmbientDisplay("test1", false); + mService.getBinderServiceInstance().suppressAmbientDisplay("test2", true); + + assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressed()).isTrue(); + } + + @Test + public void testIsAmbientDisplaySuppressed_multipleTokens_notSuppressed() throws Exception { + createService(); + mService.getBinderServiceInstance().suppressAmbientDisplay("test1", false); + mService.getBinderServiceInstance().suppressAmbientDisplay("test2", false); + + assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressed()).isFalse(); + } + + @Test + public void testIsAmbientDisplaySuppressedForToken_default_notSuppressed() throws Exception { + createService(); + + assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test")) + .isFalse(); + } + + @Test + public void testIsAmbientDisplaySuppressedForToken_suppressed() throws Exception { + createService(); + mService.getBinderServiceInstance().suppressAmbientDisplay("test", true); + + assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test")) + .isTrue(); + } + + @Test + public void testIsAmbientDisplaySuppressedForToken_notSuppressed() throws Exception { + createService(); + mService.getBinderServiceInstance().suppressAmbientDisplay("test", false); + + assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test")) + .isFalse(); + } + + @Test + public void testIsAmbientDisplaySuppressedForToken_multipleTokens_suppressed() + throws Exception { + createService(); + mService.getBinderServiceInstance().suppressAmbientDisplay("test1", true); + mService.getBinderServiceInstance().suppressAmbientDisplay("test2", true); + + assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test1")) + .isTrue(); + assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test2")) + .isTrue(); + } + + @Test + public void testIsAmbientDisplaySuppressedForToken_multipleTokens_notSuppressed() + throws Exception { + createService(); + mService.getBinderServiceInstance().suppressAmbientDisplay("test1", true); + mService.getBinderServiceInstance().suppressAmbientDisplay("test2", false); + + assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test1")) + .isTrue(); + assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test2")) + .isFalse(); + } } diff --git a/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java b/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java index 4a13dce5642b..7af3ec62e651 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java @@ -16,18 +16,18 @@ package com.android.server.usage; +import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_SYSTEM; +import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER; import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT; import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE; +import static android.app.usage.UsageStatsManager.REASON_SUB_RESTRICT_BACKGROUND_RESOURCE_USAGE; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_MOVE_TO_FOREGROUND; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE; +import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - import android.os.FileUtils; import android.test.AndroidTestCase; @@ -37,10 +37,12 @@ public class AppIdleHistoryTests extends AndroidTestCase { File mStorageDir; - final static String PACKAGE_1 = "com.android.testpackage1"; - final static String PACKAGE_2 = "com.android.testpackage2"; + private static final String PACKAGE_1 = "com.android.testpackage1"; + private static final String PACKAGE_2 = "com.android.testpackage2"; + private static final String PACKAGE_3 = "com.android.testpackage3"; + private static final String PACKAGE_4 = "com.android.testpackage4"; - final static int USER_ID = 0; + private static final int USER_ID = 0; @Override protected void setUp() throws Exception { @@ -100,16 +102,27 @@ public class AppIdleHistoryTests extends AndroidTestCase { aih.setAppStandbyBucket(PACKAGE_2, USER_ID, 2000, STANDBY_BUCKET_ACTIVE, REASON_MAIN_USAGE); + aih.setAppStandbyBucket(PACKAGE_3, USER_ID, 2500, STANDBY_BUCKET_RESTRICTED, + REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_RESTRICT_BACKGROUND_RESOURCE_USAGE); + aih.setAppStandbyBucket(PACKAGE_4, USER_ID, 2750, STANDBY_BUCKET_RESTRICTED, + REASON_MAIN_FORCED_BY_USER); aih.setAppStandbyBucket(PACKAGE_1, USER_ID, 3000, STANDBY_BUCKET_RARE, REASON_MAIN_TIMEOUT); assertEquals(aih.getAppStandbyBucket(PACKAGE_1, USER_ID, 3000), STANDBY_BUCKET_RARE); assertEquals(aih.getAppStandbyBucket(PACKAGE_2, USER_ID, 3000), STANDBY_BUCKET_ACTIVE); assertEquals(aih.getAppStandbyReason(PACKAGE_1, USER_ID, 3000), REASON_MAIN_TIMEOUT); + assertEquals(aih.getAppStandbyBucket(PACKAGE_3, USER_ID, 3000), STANDBY_BUCKET_RESTRICTED); + assertEquals(aih.getAppStandbyReason(PACKAGE_3, USER_ID, 3000), + REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_RESTRICT_BACKGROUND_RESOURCE_USAGE); + assertEquals(aih.getAppStandbyReason(PACKAGE_4, USER_ID, 3000), + REASON_MAIN_FORCED_BY_USER); - // RARE is considered idle + // RARE and RESTRICTED are considered idle assertTrue(aih.isIdle(PACKAGE_1, USER_ID, 3000)); assertFalse(aih.isIdle(PACKAGE_2, USER_ID, 3000)); + assertTrue(aih.isIdle(PACKAGE_3, USER_ID, 3000)); + assertTrue(aih.isIdle(PACKAGE_4, USER_ID, 3000)); // Check persistence aih.writeAppIdleDurations(); @@ -118,6 +131,11 @@ public class AppIdleHistoryTests extends AndroidTestCase { assertEquals(aih.getAppStandbyBucket(PACKAGE_1, USER_ID, 5000), STANDBY_BUCKET_RARE); assertEquals(aih.getAppStandbyBucket(PACKAGE_2, USER_ID, 5000), STANDBY_BUCKET_ACTIVE); assertEquals(aih.getAppStandbyReason(PACKAGE_1, USER_ID, 5000), REASON_MAIN_TIMEOUT); + assertEquals(aih.getAppStandbyBucket(PACKAGE_3, USER_ID, 3000), STANDBY_BUCKET_RESTRICTED); + assertEquals(aih.getAppStandbyReason(PACKAGE_3, USER_ID, 3000), + REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_RESTRICT_BACKGROUND_RESOURCE_USAGE); + assertEquals(aih.getAppStandbyReason(PACKAGE_4, USER_ID, 3000), + REASON_MAIN_FORCED_BY_USER); assertTrue(aih.shouldInformListeners(PACKAGE_1, USER_ID, 5000, STANDBY_BUCKET_RARE)); assertFalse(aih.shouldInformListeners(PACKAGE_1, USER_ID, 5000, STANDBY_BUCKET_RARE)); diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index 6aca58f400b3..03dc21370e24 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -28,11 +28,17 @@ import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER; import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED; import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT; import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE; +import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_EXEMPTED_SYNC_SCHEDULED_NON_DOZE; +import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_MOVE_TO_FOREGROUND; +import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SYNC_ADAPTER; +import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SYSTEM_INTERACTION; +import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_USER_INTERACTION; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_EXEMPTED; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_NEVER; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE; +import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET; import static org.junit.Assert.assertEquals; @@ -46,6 +52,8 @@ import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import android.annotation.NonNull; +import android.app.ActivityManager; import android.app.usage.AppStandbyInfo; import android.app.usage.UsageEvents; import android.appwidget.AppWidgetManager; @@ -124,6 +132,13 @@ public class AppStandbyControllerTests { public PackageManager getPackageManager() { return mockPm; } + + public Object getSystemService(@NonNull String name) { + if (Context.ACTIVITY_SERVICE.equals(name)) { + return mock(ActivityManager.class); + } + return super.getSystemService(name); + } } static class MyInjector extends AppStandbyController.Injector { @@ -253,8 +268,11 @@ public class AppStandbyControllerTests { doReturn(packages).when(mockPm).getInstalledPackagesAsUser(anyInt(), anyInt()); try { + doReturn(UID_1).when(mockPm).getPackageUidAsUser(eq(PACKAGE_1), anyInt()); doReturn(UID_1).when(mockPm).getPackageUidAsUser(eq(PACKAGE_1), anyInt(), anyInt()); doReturn(UID_EXEMPTED_1).when(mockPm).getPackageUidAsUser(eq(PACKAGE_EXEMPTED_1), + anyInt()); + doReturn(UID_EXEMPTED_1).when(mockPm).getPackageUidAsUser(eq(PACKAGE_EXEMPTED_1), anyInt(), anyInt()); doReturn(pi.applicationInfo).when(mockPm).getApplicationInfo(eq(pi.packageName), anyInt()); @@ -468,7 +486,7 @@ public class AppStandbyControllerTests { } @Test - public void testPredictionTimedout() throws Exception { + public void testPredictionTimedOut() throws Exception { // Set it to timeout or usage, so that prediction can override it mInjector.mElapsedRealtime = HOUR_MS; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, @@ -532,6 +550,79 @@ public class AppStandbyControllerTests { mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER, REASON_MAIN_PREDICTED); assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); + + // Prediction can't remove from RESTRICTED + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, + REASON_MAIN_FORCED_BY_USER); + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, + REASON_MAIN_PREDICTED); + assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1)); + + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, + REASON_MAIN_FORCED_BY_SYSTEM); + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, + REASON_MAIN_PREDICTED); + assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1)); + + // Force from user can remove from RESTRICTED + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, + REASON_MAIN_FORCED_BY_USER); + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, + REASON_MAIN_FORCED_BY_USER); + assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1)); + + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, + REASON_MAIN_FORCED_BY_SYSTEM); + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, + REASON_MAIN_FORCED_BY_USER); + assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1)); + + // Force from system can remove from RESTRICTED if it was put it in due to system + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, + REASON_MAIN_FORCED_BY_SYSTEM); + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, + REASON_MAIN_FORCED_BY_SYSTEM); + assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1)); + + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, + REASON_MAIN_FORCED_BY_USER); + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, + REASON_MAIN_FORCED_BY_SYSTEM); + assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1)); + + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, + REASON_MAIN_PREDICTED); + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, + REASON_MAIN_FORCED_BY_SYSTEM); + assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1)); + + // Non-user usage can't remove from RESTRICTED + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, + REASON_MAIN_FORCED_BY_SYSTEM); + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, + REASON_MAIN_USAGE); + assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1)); + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, + REASON_MAIN_USAGE | REASON_SUB_USAGE_SYSTEM_INTERACTION); + assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1)); + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, + REASON_MAIN_USAGE | REASON_SUB_USAGE_SYNC_ADAPTER); + assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1)); + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, + REASON_MAIN_USAGE | REASON_SUB_USAGE_EXEMPTED_SYNC_SCHEDULED_NON_DOZE); + assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1)); + + // Explicit user usage can remove from RESTRICTED + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, + REASON_MAIN_FORCED_BY_USER); + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, + REASON_MAIN_USAGE | REASON_SUB_USAGE_USER_INTERACTION); + assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1)); + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, + REASON_MAIN_FORCED_BY_SYSTEM); + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, + REASON_MAIN_USAGE | REASON_SUB_USAGE_MOVE_TO_FOREGROUND); + assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); } @Test @@ -556,6 +647,55 @@ public class AppStandbyControllerTests { assertBucket(STANDBY_BUCKET_RARE); } + /** + * Test that setAppStandbyBucket to RESTRICTED doesn't change the bucket until the usage + * timeout has passed. + */ + @Test + public void testTimeoutBeforeRestricted() throws Exception { + reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); + assertBucket(STANDBY_BUCKET_ACTIVE); + + mInjector.mElapsedRealtime += WORKING_SET_THRESHOLD; + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, + REASON_MAIN_FORCED_BY_SYSTEM); + // Bucket shouldn't change + assertBucket(STANDBY_BUCKET_ACTIVE); + + // bucketing works after timeout + mInjector.mElapsedRealtime += DAY_MS; + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, + REASON_MAIN_FORCED_BY_SYSTEM); + assertBucket(STANDBY_BUCKET_RESTRICTED); + + // Way past all timeouts. Make sure timeout processing doesn't raise bucket. + mInjector.mElapsedRealtime += RARE_THRESHOLD * 4; + mController.checkIdleStates(USER_ID); + assertBucket(STANDBY_BUCKET_RESTRICTED); + } + + /** + * Test that an app is put into the RESTRICTED bucket after enough time has passed. + */ + @Test + public void testRestrictedDelay() throws Exception { + reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); + assertBucket(STANDBY_BUCKET_ACTIVE); + + mInjector.mElapsedRealtime += mInjector.getRestrictedBucketDelayMs() - 5000; + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, + REASON_MAIN_FORCED_BY_SYSTEM); + // Bucket shouldn't change + assertBucket(STANDBY_BUCKET_ACTIVE); + + // bucketing works after timeout + mInjector.mElapsedRealtime += 6000; + + Thread.sleep(6000); + // Enough time has passed. The app should automatically be put into the RESTRICTED bucket. + assertBucket(STANDBY_BUCKET_RESTRICTED); + } + @Test public void testCascadingTimeouts() throws Exception { reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java index a9fe1a62b558..da0e03dff65a 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java @@ -117,6 +117,7 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { assertEquals(getSmartReplies(key, i), ranking.getSmartReplies()); assertEquals(canBubble(i), ranking.canBubble()); assertEquals(visuallyInterruptive(i), ranking.visuallyInterruptive()); + assertEquals(isConversation(i), ranking.isConversation()); } } @@ -184,7 +185,8 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { (ArrayList) tweak.getSmartActions(), (ArrayList) tweak.getSmartReplies(), tweak.canBubble(), - tweak.visuallyInterruptive() + tweak.visuallyInterruptive(), + tweak.isConversation() ); assertNotEquals(nru, nru2); } @@ -261,7 +263,8 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { getSmartActions(key, i), getSmartReplies(key, i), canBubble(i), - visuallyInterruptive(i) + visuallyInterruptive(i), + isConversation(i) ); rankings[i] = ranking; } @@ -370,6 +373,10 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { return index % 4 == 0; } + private boolean isConversation(int index) { + return index % 4 == 0; + } + private void assertActionsEqual( List<Notification.Action> expecteds, List<Notification.Action> actuals) { assertEquals(expecteds.size(), actuals.size()); @@ -403,6 +410,7 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { assertEquals(comment, a.isNoisy(), b.isNoisy()); assertEquals(comment, a.getSmartReplies(), b.getSmartReplies()); assertEquals(comment, a.canBubble(), b.canBubble()); + assertEquals(comment, a.isConversation(), b.isConversation()); assertActionsEqual(a.getSmartActions(), b.getSmartActions()); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java index fab6b7fd0d77..2d4b5a73bea0 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java @@ -20,6 +20,7 @@ import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.service.notification.Adjustment.KEY_IMPORTANCE; +import static android.service.notification.Adjustment.KEY_NOT_CONVERSATION; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE; @@ -38,12 +39,15 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.IActivityManager; import android.app.Notification; import android.app.Notification.Builder; import android.app.NotificationChannel; import android.app.NotificationManager; +import android.app.Person; +import android.content.ContentResolver; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -58,6 +62,7 @@ import android.os.UserHandle; import android.provider.Settings; import android.service.notification.Adjustment; import android.service.notification.StatusBarNotification; +import android.util.FeatureFlagUtils; import android.widget.RemoteViews; import androidx.test.filters.SmallTest; @@ -83,6 +88,7 @@ public class NotificationRecordTest extends UiServiceTestCase { private final Context mMockContext = mock(Context.class); @Mock private PackageManager mPm; + @Mock private ContentResolver mContentResolver; private final String pkg = PKG_N_MR1; private final int uid = 9583; @@ -116,6 +122,9 @@ public class NotificationRecordTest extends UiServiceTestCase { when(mMockContext.getResources()).thenReturn(getContext().getResources()); when(mMockContext.getPackageManager()).thenReturn(mPm); + when(mMockContext.getContentResolver()).thenReturn(mContentResolver); + Settings.Global.putString(mContentResolver, + FeatureFlagUtils.NOTIF_CONVO_BYPASS_SHORTCUT_REQ, "false"); ApplicationInfo appInfo = new ApplicationInfo(); appInfo.targetSdkVersion = Build.VERSION_CODES.O; when(mMockContext.getApplicationInfo()).thenReturn(appInfo); @@ -194,6 +203,21 @@ public class NotificationRecordTest extends UiServiceTestCase { return new StatusBarNotification(pkg, pkg, id1, tag1, uid, uid, n, mUser, null, uid); } + private StatusBarNotification getMessagingStyleNotification(@Nullable String shortcutId) { + final Builder builder = new Builder(mMockContext) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon); + + Person person = new Person.Builder().setName("Bob").build(); + builder.setStyle(new Notification.MessagingStyle(person)); + if (shortcutId != null) { + builder.setShortcutId(shortcutId); + } + + Notification n = builder.build(); + return new StatusBarNotification(pkg, pkg, id1, tag1, uid, uid, n, mUser, null, uid); + } + // // Tests // @@ -1095,4 +1119,55 @@ public class NotificationRecordTest extends UiServiceTestCase { assertTrue("false negative detection", record.hasUndecoratedRemoteView()); } + + @Test + public void testIsConversation() { + StatusBarNotification sbn = getMessagingStyleNotification("test_shortcut_id"); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel); + + assertTrue(record.isConversation()); + } + + @Test + public void testIsConversation_nullShortcutId() { + StatusBarNotification sbn = getMessagingStyleNotification(null); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel); + + assertFalse(record.isConversation()); + } + + @Test + public void testIsConversation_bypassShortcutFlagEnabled() { + Settings.Global.putString(mContentResolver, + FeatureFlagUtils.NOTIF_CONVO_BYPASS_SHORTCUT_REQ, "true"); + StatusBarNotification sbn = getMessagingStyleNotification(null); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel); + + assertTrue(record.isConversation()); + } + + @Test + public void testIsConversation_channelDemoted() { + StatusBarNotification sbn = getMessagingStyleNotification("test_shortcut_id"); + channel.setDemoted(true); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel); + + assertFalse(record.isConversation()); + } + + @Test + public void testIsConversation_withAdjustmentOverride() { + StatusBarNotification sbn = getMessagingStyleNotification("test_shortcut_id"); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel); + + Bundle bundle = new Bundle(); + bundle.putBoolean(KEY_NOT_CONVERSATION, true); + Adjustment adjustment = new Adjustment( + PKG_O, record.getKey(), bundle, "", record.getUser().getIdentifier()); + + record.addAdjustment(adjustment); + record.applyAdjustments(); + + assertFalse(record.isConversation()); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java index 0527561880f2..a95f43f41a88 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java @@ -36,7 +36,6 @@ import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_M import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; @@ -216,7 +215,6 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL); mWindow.mAttrs.flags = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; - mWindow.mAttrs.privateFlags = PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND; mWindow.mAttrs.setFitWindowInsetsTypes(Type.systemBars()); addWindow(mWindow); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java index e699b526e848..c370d6c7c516 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java @@ -28,6 +28,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; @@ -233,6 +234,16 @@ public class DisplayPolicyTests extends WindowTestsBase { assertNotEquals(0, toast.getAttrs().flags & FLAG_SHOW_WHEN_LOCKED); } + @Test(expected = RuntimeException.class) + public void testMainAppWindowDisallowFitSystemWindowTypes() { + final DisplayPolicy policy = mDisplayContent.getDisplayPolicy(); + final WindowState activity = createBaseApplicationWindow(); + activity.mAttrs.privateFlags |= PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS; + + policy.adjustWindowParamsLw(activity, activity.mAttrs, 0 /* callingPid */, + 0 /* callingUid */); + } + private WindowState createToastWindow() { final WindowState win = createWindow(null, TYPE_TOAST, "Toast"); final WindowManager.LayoutParams attrs = win.mAttrs; @@ -254,6 +265,17 @@ public class DisplayPolicyTests extends WindowTestsBase { return win; } + private WindowState createBaseApplicationWindow() { + final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, "Application"); + final WindowManager.LayoutParams attrs = win.mAttrs; + attrs.width = MATCH_PARENT; + attrs.height = MATCH_PARENT; + attrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR; + attrs.format = PixelFormat.OPAQUE; + win.mHasSurface = true; + return win; + } + @Test @FlakyTest(bugId = 131005232) public void testOverlappingWithNavBar() { diff --git a/services/usb/Android.bp b/services/usb/Android.bp index d2c973abbc74..a9474c10017e 100644 --- a/services/usb/Android.bp +++ b/services/usb/Android.bp @@ -19,5 +19,6 @@ java_library_static { "android.hardware.usb-V1.1-java", "android.hardware.usb-V1.2-java", "android.hardware.usb.gadget-V1.0-java", + "android.hardware.usb.gadget-V1.1-java", ], } diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index 9f3b07b951be..6407ec76958e 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -161,6 +161,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser private static final int MSG_GET_CURRENT_USB_FUNCTIONS = 16; private static final int MSG_FUNCTION_SWITCH_TIMEOUT = 17; private static final int MSG_GADGET_HAL_REGISTERED = 18; + private static final int MSG_RESET_USB_GADGET = 19; private static final int AUDIO_MODE_SOURCE = 1; @@ -1846,6 +1847,23 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } } break; + case MSG_RESET_USB_GADGET: + synchronized (mGadgetProxyLock) { + if (mGadgetProxy == null) { + Slog.e(TAG, "reset Usb Gadget mGadgetProxy is null"); + break; + } + + try { + android.hardware.usb.gadget.V1_1.IUsbGadget gadgetProxy = + android.hardware.usb.gadget.V1_1.IUsbGadget + .castFrom(mGadgetProxy); + gadgetProxy.reset(); + } catch (RemoteException e) { + Slog.e(TAG, "reset Usb Gadget failed", e); + } + } + break; default: super.handleMessage(msg); } @@ -2054,6 +2072,17 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser mHandler.sendMessage(MSG_SET_SCREEN_UNLOCKED_FUNCTIONS, functions); } + /** + * Resets the USB Gadget. + */ + public void resetUsbGadget() { + if (DEBUG) { + Slog.d(TAG, "reset Usb Gadget"); + } + + mHandler.sendMessage(MSG_RESET_USB_GADGET, null); + } + private void onAdbEnabled(boolean enabled) { mHandler.sendMessage(MSG_ENABLE_ADB, enabled); } diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java index b1bd04ecb13f..61f2c50ccecf 100644 --- a/services/usb/java/com/android/server/usb/UsbService.java +++ b/services/usb/java/com/android/server/usb/UsbService.java @@ -66,6 +66,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.concurrent.CompletableFuture; /** @@ -345,7 +346,7 @@ public class UsbService extends IUsbManager.Stub { @Override public void setDevicePackage(UsbDevice device, String packageName, int userId) { - device = Preconditions.checkNotNull(device); + Objects.requireNonNull(device); mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); @@ -361,7 +362,7 @@ public class UsbService extends IUsbManager.Stub { @Override public void setAccessoryPackage(UsbAccessory accessory, String packageName, int userId) { - accessory = Preconditions.checkNotNull(accessory); + Objects.requireNonNull(accessory); mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); @@ -379,9 +380,9 @@ public class UsbService extends IUsbManager.Stub { @Override public void addDevicePackagesToPreferenceDenied(UsbDevice device, String[] packageNames, UserHandle user) { - device = Preconditions.checkNotNull(device); + Objects.requireNonNull(device); packageNames = Preconditions.checkArrayElementsNotNull(packageNames, "packageNames"); - user = Preconditions.checkNotNull(user); + Objects.requireNonNull(user); mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); @@ -397,9 +398,9 @@ public class UsbService extends IUsbManager.Stub { @Override public void addAccessoryPackagesToPreferenceDenied(UsbAccessory accessory, String[] packageNames, UserHandle user) { - accessory = Preconditions.checkNotNull(accessory); + Objects.requireNonNull(accessory); packageNames = Preconditions.checkArrayElementsNotNull(packageNames, "packageNames"); - user = Preconditions.checkNotNull(user); + Objects.requireNonNull(user); mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); @@ -415,9 +416,9 @@ public class UsbService extends IUsbManager.Stub { @Override public void removeDevicePackagesFromPreferenceDenied(UsbDevice device, String[] packageNames, UserHandle user) { - device = Preconditions.checkNotNull(device); + Objects.requireNonNull(device); packageNames = Preconditions.checkArrayElementsNotNull(packageNames, "packageNames"); - user = Preconditions.checkNotNull(user); + Objects.requireNonNull(user); mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); @@ -433,9 +434,9 @@ public class UsbService extends IUsbManager.Stub { @Override public void removeAccessoryPackagesFromPreferenceDenied(UsbAccessory accessory, String[] packageNames, UserHandle user) { - accessory = Preconditions.checkNotNull(accessory); + Objects.requireNonNull(accessory); packageNames = Preconditions.checkArrayElementsNotNull(packageNames, "packageNames"); - user = Preconditions.checkNotNull(user); + Objects.requireNonNull(user); mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); @@ -451,8 +452,8 @@ public class UsbService extends IUsbManager.Stub { @Override public void setDevicePersistentPermission(UsbDevice device, int uid, UserHandle user, boolean shouldBeGranted) { - device = Preconditions.checkNotNull(device); - user = Preconditions.checkNotNull(user); + Objects.requireNonNull(device); + Objects.requireNonNull(user); mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); @@ -468,8 +469,8 @@ public class UsbService extends IUsbManager.Stub { @Override public void setAccessoryPersistentPermission(UsbAccessory accessory, int uid, UserHandle user, boolean shouldBeGranted) { - accessory = Preconditions.checkNotNull(accessory); - user = Preconditions.checkNotNull(user); + Objects.requireNonNull(accessory); + Objects.requireNonNull(user); mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); @@ -637,6 +638,19 @@ public class UsbService extends IUsbManager.Stub { } @Override + public void resetUsbGadget() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); + Preconditions.checkNotNull(mDeviceManager, "DeviceManager must not be null"); + + final long ident = Binder.clearCallingIdentity(); + try { + mDeviceManager.resetUsbGadget(); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override public List<ParcelableUsbPort> getPorts() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); @@ -663,7 +677,7 @@ public class UsbService extends IUsbManager.Stub { @Override public UsbPortStatus getPortStatus(String portId) { - Preconditions.checkNotNull(portId, "portId must not be null"); + Objects.requireNonNull(portId, "portId must not be null"); mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); final long ident = Binder.clearCallingIdentity(); @@ -676,7 +690,7 @@ public class UsbService extends IUsbManager.Stub { @Override public void setPortRoles(String portId, int powerRole, int dataRole) { - Preconditions.checkNotNull(portId, "portId must not be null"); + Objects.requireNonNull(portId, "portId must not be null"); UsbPort.checkRoles(powerRole, dataRole); mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); @@ -692,7 +706,7 @@ public class UsbService extends IUsbManager.Stub { @Override public void enableContaminantDetection(String portId, boolean enable) { - Preconditions.checkNotNull(portId, "portId must not be null"); + Objects.requireNonNull(portId, "portId must not be null"); mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); final long ident = Binder.clearCallingIdentity(); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java index c58b6da64baa..af81ab6339f3 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java @@ -46,18 +46,21 @@ public class DatabaseHelper extends SQLiteOpenHelper { private static final String NAME = "sound_model.db"; private static final int VERSION = 7; - public static interface SoundModelContract { - public static final String TABLE = "sound_model"; - public static final String KEY_MODEL_UUID = "model_uuid"; - public static final String KEY_VENDOR_UUID = "vendor_uuid"; - public static final String KEY_KEYPHRASE_ID = "keyphrase_id"; - public static final String KEY_TYPE = "type"; - public static final String KEY_DATA = "data"; - public static final String KEY_RECOGNITION_MODES = "recognition_modes"; - public static final String KEY_LOCALE = "locale"; - public static final String KEY_HINT_TEXT = "hint_text"; - public static final String KEY_USERS = "users"; - public static final String KEY_MODEL_VERSION = "model_version"; + /** + * Keyphrase sound model database columns + */ + public interface SoundModelContract { + String TABLE = "sound_model"; + String KEY_MODEL_UUID = "model_uuid"; + String KEY_VENDOR_UUID = "vendor_uuid"; + String KEY_KEYPHRASE_ID = "keyphrase_id"; + String KEY_TYPE = "type"; + String KEY_DATA = "data"; + String KEY_RECOGNITION_MODES = "recognition_modes"; + String KEY_LOCALE = "locale"; + String KEY_HINT_TEXT = "hint_text"; + String KEY_USERS = "users"; + String KEY_MODEL_VERSION = "model_version"; } // Table Create Statement @@ -173,7 +176,8 @@ public class DatabaseHelper extends SQLiteOpenHelper { soundModel.keyphrases[0].recognitionModes); values.put(SoundModelContract.KEY_USERS, getCommaSeparatedString(soundModel.keyphrases[0].users)); - values.put(SoundModelContract.KEY_LOCALE, soundModel.keyphrases[0].locale); + values.put(SoundModelContract.KEY_LOCALE, + soundModel.keyphrases[0].locale.toLanguageTag()); values.put(SoundModelContract.KEY_HINT_TEXT, soundModel.keyphrases[0].text); try { return db.insertWithOnConflict(SoundModelContract.TABLE, null, values, @@ -190,7 +194,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { * Deletes the sound model and associated keyphrases. */ public boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale) { - // Sanitize the locale to guard against SQL injection. + // Normalize the locale to guard against SQL injection. bcp47Locale = Locale.forLanguageTag(bcp47Locale).toLanguageTag(); synchronized(this) { KeyphraseSoundModel soundModel = getKeyphraseSoundModel(keyphraseId, userHandle, @@ -226,90 +230,117 @@ public class DatabaseHelper extends SQLiteOpenHelper { String selectQuery = "SELECT * FROM " + SoundModelContract.TABLE + " WHERE " + SoundModelContract.KEY_KEYPHRASE_ID + "= '" + keyphraseId + "' AND " + SoundModelContract.KEY_LOCALE + "='" + bcp47Locale + "'"; - SQLiteDatabase db = getReadableDatabase(); - Cursor c = db.rawQuery(selectQuery, null); + return getValidKeyphraseSoundModelForUser(selectQuery, userHandle); + } + } - try { - if (c.moveToFirst()) { - do { - int type = c.getInt(c.getColumnIndex(SoundModelContract.KEY_TYPE)); - if (type != SoundTrigger.SoundModel.TYPE_KEYPHRASE) { - if (DBG) { - Slog.w(TAG, "Ignoring SoundModel since it's type is incorrect"); - } - continue; - } + /** + * Returns a matching {@link KeyphraseSoundModel} for the keyphrase string. + * Returns null if a match isn't found. + * + * TODO: We only support one keyphrase currently. + */ + public KeyphraseSoundModel getKeyphraseSoundModel(String keyphrase, int userHandle, + String bcp47Locale) { + // Sanitize the locale to guard against SQL injection. + bcp47Locale = Locale.forLanguageTag(bcp47Locale).toLanguageTag(); + synchronized (this) { + // Find the corresponding sound model ID for the keyphrase. + String selectQuery = "SELECT * FROM " + SoundModelContract.TABLE + + " WHERE " + SoundModelContract.KEY_HINT_TEXT + "= '" + keyphrase + + "' AND " + SoundModelContract.KEY_LOCALE + "='" + bcp47Locale + "'"; + return getValidKeyphraseSoundModelForUser(selectQuery, userHandle); + } + } - String modelUuid = c.getString( - c.getColumnIndex(SoundModelContract.KEY_MODEL_UUID)); - if (modelUuid == null) { - Slog.w(TAG, "Ignoring SoundModel since it doesn't specify an ID"); - continue; - } + private KeyphraseSoundModel getValidKeyphraseSoundModelForUser(String selectQuery, + int userHandle) { + SQLiteDatabase db = getReadableDatabase(); + Cursor c = db.rawQuery(selectQuery, null); - String vendorUuidString = null; - int vendorUuidColumn = c.getColumnIndex(SoundModelContract.KEY_VENDOR_UUID); - if (vendorUuidColumn != -1) { - vendorUuidString = c.getString(vendorUuidColumn); - } - byte[] data = c.getBlob(c.getColumnIndex(SoundModelContract.KEY_DATA)); - int recognitionModes = c.getInt( - c.getColumnIndex(SoundModelContract.KEY_RECOGNITION_MODES)); - int[] users = getArrayForCommaSeparatedString( - c.getString(c.getColumnIndex(SoundModelContract.KEY_USERS))); - String modelLocale = c.getString( - c.getColumnIndex(SoundModelContract.KEY_LOCALE)); - String text = c.getString( - c.getColumnIndex(SoundModelContract.KEY_HINT_TEXT)); - int version = c.getInt( - c.getColumnIndex(SoundModelContract.KEY_MODEL_VERSION)); - - // Only add keyphrases meant for the current user. - if (users == null) { - // No users present in the keyphrase. - Slog.w(TAG, "Ignoring SoundModel since it doesn't specify users"); - continue; + try { + if (c.moveToFirst()) { + do { + int type = c.getInt(c.getColumnIndex(SoundModelContract.KEY_TYPE)); + if (type != SoundTrigger.SoundModel.TYPE_KEYPHRASE) { + if (DBG) { + Slog.w(TAG, "Ignoring SoundModel since its type is incorrect"); } + continue; + } - boolean isAvailableForCurrentUser = false; - for (int user : users) { - if (userHandle == user) { - isAvailableForCurrentUser = true; - break; - } - } - if (!isAvailableForCurrentUser) { - if (DBG) { - Slog.w(TAG, "Ignoring SoundModel since user handles don't match"); - } - continue; - } else { - if (DBG) Slog.d(TAG, "Found a SoundModel for user: " + userHandle); - } + String modelUuid = c.getString( + c.getColumnIndex(SoundModelContract.KEY_MODEL_UUID)); + if (modelUuid == null) { + Slog.w(TAG, "Ignoring SoundModel since it doesn't specify an ID"); + continue; + } + + String vendorUuidString = null; + int vendorUuidColumn = c.getColumnIndex(SoundModelContract.KEY_VENDOR_UUID); + if (vendorUuidColumn != -1) { + vendorUuidString = c.getString(vendorUuidColumn); + } + int keyphraseId = c.getInt( + c.getColumnIndex(SoundModelContract.KEY_KEYPHRASE_ID)); + byte[] data = c.getBlob(c.getColumnIndex(SoundModelContract.KEY_DATA)); + int recognitionModes = c.getInt( + c.getColumnIndex(SoundModelContract.KEY_RECOGNITION_MODES)); + int[] users = getArrayForCommaSeparatedString( + c.getString(c.getColumnIndex(SoundModelContract.KEY_USERS))); + Locale modelLocale = Locale.forLanguageTag(c.getString( + c.getColumnIndex(SoundModelContract.KEY_LOCALE))); + String text = c.getString( + c.getColumnIndex(SoundModelContract.KEY_HINT_TEXT)); + int version = c.getInt( + c.getColumnIndex(SoundModelContract.KEY_MODEL_VERSION)); + + // Only add keyphrases meant for the current user. + if (users == null) { + // No users present in the keyphrase. + Slog.w(TAG, "Ignoring SoundModel since it doesn't specify users"); + continue; + } - Keyphrase[] keyphrases = new Keyphrase[1]; - keyphrases[0] = new Keyphrase( - keyphraseId, recognitionModes, modelLocale, text, users); - UUID vendorUuid = null; - if (vendorUuidString != null) { - vendorUuid = UUID.fromString(vendorUuidString); + boolean isAvailableForCurrentUser = false; + for (int user : users) { + if (userHandle == user) { + isAvailableForCurrentUser = true; + break; } - KeyphraseSoundModel model = new KeyphraseSoundModel( - UUID.fromString(modelUuid), vendorUuid, data, keyphrases, version); + } + if (!isAvailableForCurrentUser) { if (DBG) { - Slog.d(TAG, "Found SoundModel for the given keyphrase/locale/user: " - + model); + Slog.w(TAG, "Ignoring SoundModel since user handles don't match"); } - return model; - } while (c.moveToNext()); - } - Slog.w(TAG, "No SoundModel available for the given keyphrase"); - } finally { - c.close(); - db.close(); + continue; + } else { + if (DBG) Slog.d(TAG, "Found a SoundModel for user: " + userHandle); + } + + Keyphrase[] keyphrases = new Keyphrase[1]; + keyphrases[0] = new Keyphrase( + keyphraseId, recognitionModes, modelLocale, text, users); + UUID vendorUuid = null; + if (vendorUuidString != null) { + vendorUuid = UUID.fromString(vendorUuidString); + } + KeyphraseSoundModel model = new KeyphraseSoundModel( + UUID.fromString(modelUuid), vendorUuid, data, keyphrases, version); + if (DBG) { + Slog.d(TAG, "Found SoundModel for the given keyphrase/locale/user: " + + model); + } + return model; + } while (c.moveToNext()); } - return null; + Slog.w(TAG, "No SoundModel available for the given keyphrase"); + } finally { + c.close(); + db.close(); } + + return null; } private static String getCommaSeparatedString(int[] users) { @@ -431,8 +462,11 @@ public class DatabaseHelper extends SQLiteOpenHelper { } } + /** + * Dumps contents of database for dumpsys + */ public void dump(PrintWriter pw) { - synchronized(this) { + synchronized (this) { String selectQuery = "SELECT * FROM " + SoundModelContract.TABLE; SQLiteDatabase db = getReadableDatabase(); Cursor c = db.rawQuery(selectQuery, null); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 506c67e12528..d5eec332cda0 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -41,7 +41,9 @@ import android.content.pm.UserInfo; import android.content.res.Resources; import android.database.ContentObserver; import android.hardware.soundtrigger.IRecognitionStatusCallback; +import android.hardware.soundtrigger.KeyphraseMetadata; import android.hardware.soundtrigger.ModelParams; +import android.hardware.soundtrigger.SoundTrigger; import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; import android.hardware.soundtrigger.SoundTrigger.ModelParamRange; import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; @@ -90,6 +92,7 @@ import com.android.server.wm.ActivityTaskManagerInternal; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.List; +import java.util.Locale; import java.util.Objects; import java.util.concurrent.Executor; @@ -923,6 +926,8 @@ public class VoiceInteractionManagerService extends SystemService { } //----------------- Model management APIs --------------------------------// + // TODO: add check to only allow active voice interaction service or keyphrase enrollment + // application to manage voice models @Override public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, String bcp47Locale) { @@ -1022,6 +1027,41 @@ public class VoiceInteractionManagerService extends SystemService { } } + @Nullable + public KeyphraseMetadata getEnrolledKeyphraseMetadata(IVoiceInteractionService service, + String keyphrase, String bcp47Locale) { + synchronized (this) { + enforceIsCurrentVoiceInteractionService(service); + } + + if (bcp47Locale == null) { + throw new IllegalArgumentException("Illegal argument(s) in isEnrolledForKeyphrase"); + } + + final int callingUid = UserHandle.getCallingUserId(); + final long caller = Binder.clearCallingIdentity(); + try { + KeyphraseSoundModel model = + mDbHelper.getKeyphraseSoundModel(keyphrase, callingUid, bcp47Locale); + if (model == null) { + return null; + } + + for (SoundTrigger.Keyphrase phrase : model.keyphrases) { + if (keyphrase.equals(phrase.text)) { + ArraySet<Locale> locales = new ArraySet<>(); + locales.add(phrase.locale); + return new KeyphraseMetadata(phrase.id, phrase.text, locales, + phrase.recognitionModes); + } + } + } finally { + Binder.restoreCallingIdentity(caller); + } + + return null; + } + @Override public ModuleProperties getDspModuleProperties(IVoiceInteractionService service) { // Allow the call if this is the current voice interaction service. diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index 826a89eb38bb..acf51f3856d3 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -547,8 +547,14 @@ public final class Call { */ public static final int PROPERTY_VOIP_AUDIO_MODE = 0x00001000; + /** + * Indicates that the call is an adhoc conference call. This property can be set for both + * incoming and outgoing calls. + */ + public static final int PROPERTY_IS_ADHOC_CONFERENCE = 0x00002000; + //****************************************************************************************** - // Next PROPERTY value: 0x00002000 + // Next PROPERTY value: 0x00004000 //****************************************************************************************** private final String mTelecomCallId; @@ -726,6 +732,9 @@ public final class Call { if (hasProperty(properties, PROPERTY_VOIP_AUDIO_MODE)) { builder.append(" PROPERTY_VOIP_AUDIO_MODE"); } + if (hasProperty(properties, PROPERTY_IS_ADHOC_CONFERENCE)) { + builder.append(" PROPERTY_IS_ADHOC_CONFERENCE"); + } builder.append("]"); return builder.toString(); } diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java index 456290cd772a..6b0845f5d12b 100644 --- a/telecomm/java/android/telecom/Conference.java +++ b/telecomm/java/android/telecom/Conference.java @@ -69,6 +69,7 @@ public abstract class Conference extends Conferenceable { public void onConnectionEvent(Conference c, String event, Bundle extras) {} public void onCallerDisplayNameChanged( Conference c, String callerDisplayName, int presentation) {} + public void onRingbackRequested(Conference c, boolean ringback) {} } private final Set<Listener> mListeners = new CopyOnWriteArraySet<>(); @@ -97,6 +98,7 @@ public abstract class Conference extends Conferenceable { private int mAddressPresentation; private String mCallerDisplayName; private int mCallerDisplayNamePresentation; + private boolean mRingbackRequested = false; private final Connection.Listener mConnectionDeathListener = new Connection.Listener() { @Override @@ -170,6 +172,14 @@ public abstract class Conference extends Conferenceable { } /** + * Returns whether this conference is requesting that the system play a ringback tone + * on its behalf. + */ + public final boolean isRingbackRequested() { + return mRingbackRequested; + } + + /** * Returns the capabilities of the conference. See {@code CAPABILITY_*} constants in class * {@link Connection} for valid values. * @@ -308,6 +318,35 @@ public abstract class Conference extends Conferenceable { public void onConnectionAdded(Connection connection) {} /** + * Notifies this Conference, which is in {@code STATE_RINGING}, of + * a request to accept. + * For managed {@link ConnectionService}s, this will be called when the user answers a call via + * the default dialer's {@link InCallService}. + * + * @param videoState The video state in which to answer the connection. + */ + public void onAnswer(int videoState) {} + + /** + * Notifies this Conference, which is in {@code STATE_RINGING}, of + * a request to accept. + * For managed {@link ConnectionService}s, this will be called when the user answers a call via + * the default dialer's {@link InCallService}. + * @hide + */ + public final void onAnswer() { + onAnswer(VideoProfile.STATE_AUDIO_ONLY); + } + + /** + * Notifies this Conference, which is in {@code STATE_RINGING}, of + * a request to reject. + * For managed {@link ConnectionService}s, this will be called when the user rejects a call via + * the default dialer's {@link InCallService}. + */ + public void onReject() {} + + /** * Sets state to be on hold. */ public final void setOnHold() { @@ -322,9 +361,17 @@ public abstract class Conference extends Conferenceable { } /** + * Sets state to be ringing. + */ + public final void setRinging() { + setState(Connection.STATE_RINGING); + } + + /** * Sets state to be active. */ public final void setActive() { + setRingbackRequested(false); setState(Connection.STATE_ACTIVE); } @@ -436,6 +483,21 @@ public abstract class Conference extends Conferenceable { } /** + * Requests that the framework play a ringback tone. This is to be invoked by implementations + * that do not play a ringback tone themselves in the conference's audio stream. + * + * @param ringback Whether the ringback tone is to be played. + */ + public final void setRingbackRequested(boolean ringback) { + if (mRingbackRequested != ringback) { + mRingbackRequested = ringback; + for (Listener l : mListeners) { + l.onRingbackRequested(this, ringback); + } + } + } + + /** * Set the video state for the conference. * Valid values: {@link VideoProfile#STATE_AUDIO_ONLY}, * {@link VideoProfile#STATE_BIDIRECTIONAL}, @@ -640,14 +702,6 @@ public abstract class Conference extends Conferenceable { } private void setState(int newState) { - if (newState != Connection.STATE_ACTIVE && - newState != Connection.STATE_HOLDING && - newState != Connection.STATE_DISCONNECTED) { - Log.w(this, "Unsupported state transition for Conference call.", - Connection.stateToString(newState)); - return; - } - if (mState != newState) { int oldState = mState; mState = newState; @@ -657,6 +711,37 @@ public abstract class Conference extends Conferenceable { } } + private static class FailureSignalingConference extends Conference { + private boolean mImmutable = false; + public FailureSignalingConference(DisconnectCause disconnectCause, + PhoneAccountHandle phoneAccount) { + super(phoneAccount); + setDisconnected(disconnectCause); + mImmutable = true; + } + public void checkImmutable() { + if (mImmutable) { + throw new UnsupportedOperationException("Conference is immutable"); + } + } + } + + /** + * Return a {@code Conference} which represents a failed conference attempt. The returned + * {@code Conference} will have a {@link android.telecom.DisconnectCause} and as specified, + * and a {@link #getState()} of {@code STATE_DISCONNECTED}. + * <p> + * The returned {@code Conference} can be assumed to {@link #destroy()} itself when appropriate, + * so users of this method need not maintain a reference to its return value to destroy it. + * + * @param disconnectCause The disconnect cause, ({@see android.telecomm.DisconnectCause}). + * @return A {@code Conference} which indicates failure. + */ + public @NonNull static Conference createFailedConference( + @NonNull DisconnectCause disconnectCause, @NonNull PhoneAccountHandle phoneAccount) { + return new FailureSignalingConference(disconnectCause, phoneAccount); + } + private final void clearConferenceableList() { for (Connection c : mConferenceableConnections) { c.removeConnectionListener(mConnectionDeathListener); @@ -667,11 +752,13 @@ public abstract class Conference extends Conferenceable { @Override public String toString() { return String.format(Locale.US, - "[State: %s,Capabilites: %s, VideoState: %s, VideoProvider: %s, ThisObject %s]", + "[State: %s,Capabilites: %s, VideoState: %s, VideoProvider: %s," + + "isRingbackRequested: %s, ThisObject %s]", Connection.stateToString(mState), Call.Details.capabilitiesToString(mConnectionCapabilities), getVideoState(), getVideoProvider(), + isRingbackRequested() ? "Y" : "N", super.toString()); } diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index f205ec64f49b..c934625f588b 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -497,8 +497,17 @@ public abstract class Connection extends Conferenceable { @TestApi public static final int PROPERTY_REMOTELY_HOSTED = 1 << 11; + /** + * Set by the framework to indicate that it is an adhoc conference call. + * <p> + * This is used for Outgoing and incoming conference calls. + * + */ + public static final int PROPERTY_IS_ADHOC_CONFERENCE = 1 << 12; + + //********************************************************************************************** - // Next PROPERTY value: 1<<12 + // Next PROPERTY value: 1<<13 //********************************************************************************************** /** @@ -1018,6 +1027,10 @@ public abstract class Connection extends Conferenceable { builder.append(isLong ? " PROPERTY_REMOTELY_HOSTED" : " remote_hst"); } + if ((properties & PROPERTY_IS_ADHOC_CONFERENCE) == PROPERTY_IS_ADHOC_CONFERENCE) { + builder.append(isLong ? " PROPERTY_IS_ADHOC_CONFERENCE" : " adhoc_conf"); + } + builder.append("]"); return builder.toString(); } diff --git a/telecomm/java/android/telecom/ConnectionRequest.java b/telecomm/java/android/telecom/ConnectionRequest.java index 221f8f129744..6d7ceca0a2cd 100644 --- a/telecomm/java/android/telecom/ConnectionRequest.java +++ b/telecomm/java/android/telecom/ConnectionRequest.java @@ -26,6 +26,9 @@ import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; +import java.util.ArrayList; +import java.util.List; + /** * Simple data container encapsulating a request to some entity to * create a new {@link Connection}. @@ -46,6 +49,8 @@ public final class ConnectionRequest implements Parcelable { private boolean mShouldShowIncomingCallUi = false; private ParcelFileDescriptor mRttPipeToInCall; private ParcelFileDescriptor mRttPipeFromInCall; + private List<Uri> mParticipants; + private boolean mIsAdhocConference = false; public Builder() { } @@ -59,6 +64,15 @@ public final class ConnectionRequest implements Parcelable { } /** + * Sets the participants for the resulting {@link ConnectionRequest} + * @param participants The participants to which the {@link Connection} is to connect. + */ + public @NonNull Builder setParticipants(@Nullable List<Uri> participants) { + this.mParticipants = participants; + return this; + } + + /** * Sets the address for the resulting {@link ConnectionRequest} * @param address The address(e.g., phone number) to which the {@link Connection} is to * connect. @@ -108,6 +122,16 @@ public final class ConnectionRequest implements Parcelable { } /** + * Sets isAdhocConference for the resulting {@link ConnectionRequest} + * @param isAdhocConference {@code true} if it is a adhoc conference call + * {@code false}, if not a adhoc conference call + */ + public @NonNull Builder setIsAdhocConferenceCall(boolean isAdhocConference) { + this.mIsAdhocConference = isAdhocConference; + return this; + } + + /** * Sets the RTT pipe for transferring text into the {@link ConnectionService} for the * resulting {@link ConnectionRequest} * @param rttPipeFromInCall The data pipe to read from. @@ -141,7 +165,9 @@ public final class ConnectionRequest implements Parcelable { mTelecomCallId, mShouldShowIncomingCallUi, mRttPipeFromInCall, - mRttPipeToInCall); + mRttPipeToInCall, + mParticipants, + mIsAdhocConference); } } @@ -155,6 +181,8 @@ public final class ConnectionRequest implements Parcelable { private final ParcelFileDescriptor mRttPipeFromInCall; // Cached return value of getRttTextStream -- we don't want to wrap it more than once. private Connection.RttTextStream mRttTextStream; + private List<Uri> mParticipants; + private final boolean mIsAdhocConference; /** * @param accountHandle The accountHandle which should be used to place the call. @@ -214,6 +242,21 @@ public final class ConnectionRequest implements Parcelable { boolean shouldShowIncomingCallUi, ParcelFileDescriptor rttPipeFromInCall, ParcelFileDescriptor rttPipeToInCall) { + this(accountHandle, handle, extras, videoState, telecomCallId, + shouldShowIncomingCallUi, rttPipeFromInCall, rttPipeToInCall, null, false); + } + + private ConnectionRequest( + PhoneAccountHandle accountHandle, + Uri handle, + Bundle extras, + int videoState, + String telecomCallId, + boolean shouldShowIncomingCallUi, + ParcelFileDescriptor rttPipeFromInCall, + ParcelFileDescriptor rttPipeToInCall, + List<Uri> participants, + boolean isAdhocConference) { mAccountHandle = accountHandle; mAddress = handle; mExtras = extras; @@ -222,6 +265,8 @@ public final class ConnectionRequest implements Parcelable { mShouldShowIncomingCallUi = shouldShowIncomingCallUi; mRttPipeFromInCall = rttPipeFromInCall; mRttPipeToInCall = rttPipeToInCall; + mParticipants = participants; + mIsAdhocConference = isAdhocConference; } private ConnectionRequest(Parcel in) { @@ -233,6 +278,11 @@ public final class ConnectionRequest implements Parcelable { mShouldShowIncomingCallUi = in.readInt() == 1; mRttPipeFromInCall = in.readParcelable(getClass().getClassLoader()); mRttPipeToInCall = in.readParcelable(getClass().getClassLoader()); + + mParticipants = new ArrayList<Uri>(); + in.readList(mParticipants, getClass().getClassLoader()); + + mIsAdhocConference = in.readInt() == 1; } /** @@ -246,6 +296,11 @@ public final class ConnectionRequest implements Parcelable { public Uri getAddress() { return mAddress; } /** + * The participants to which the {@link Connection} is to connect. + */ + public @Nullable List<Uri> getParticipants() { return mParticipants; } + + /** * Application-specific extra data. Used for passing back information from an incoming * call {@code Intent}, and for any proprietary extensions arranged between a client * and servant {@code ConnectionService} which agree on a vocabulary for such data. @@ -290,6 +345,13 @@ public final class ConnectionRequest implements Parcelable { } /** + * @return {@code true} if the call is a adhoc conference call else @return {@code false} + */ + public boolean isAdhocConferenceCall() { + return mIsAdhocConference; + } + + /** * Gets the {@link ParcelFileDescriptor} that is used to send RTT text from the connection * service to the in-call UI. In order to obtain an * {@link java.io.InputStream} from this {@link ParcelFileDescriptor}, use @@ -345,11 +407,12 @@ public final class ConnectionRequest implements Parcelable { @Override public String toString() { - return String.format("ConnectionRequest %s %s", + return String.format("ConnectionRequest %s %s isAdhocConf: %s", mAddress == null ? Uri.EMPTY : Connection.toLogSafePhoneNumber(mAddress.toString()), - bundleToString(mExtras)); + bundleToString(mExtras), + isAdhocConferenceCall() ? "Y" : "N"); } private static String bundleToString(Bundle extras){ @@ -406,5 +469,7 @@ public final class ConnectionRequest implements Parcelable { destination.writeInt(mShouldShowIncomingCallUi ? 1 : 0); destination.writeParcelable(mRttPipeFromInCall, 0); destination.writeParcelable(mRttPipeToInCall, 0); + destination.writeList(mParticipants); + destination.writeInt(mIsAdhocConference ? 1 : 0); } } diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java index 3a0494e17db9..440f044fdcf7 100644 --- a/telecomm/java/android/telecom/ConnectionService.java +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -154,6 +154,9 @@ public abstract class ConnectionService extends Service { private static final String SESSION_CONNECTION_SERVICE_FOCUS_LOST = "CS.cSFL"; private static final String SESSION_CONNECTION_SERVICE_FOCUS_GAINED = "CS.cSFG"; private static final String SESSION_HANDOVER_FAILED = "CS.haF"; + private static final String SESSION_CREATE_CONF = "CS.crConf"; + private static final String SESSION_CREATE_CONF_COMPLETE = "CS.crConfC"; + private static final String SESSION_CREATE_CONF_FAILED = "CS.crConfF"; private static final int MSG_ADD_CONNECTION_SERVICE_ADAPTER = 1; private static final int MSG_CREATE_CONNECTION = 2; @@ -188,6 +191,9 @@ public abstract class ConnectionService extends Service { private static final int MSG_HANDOVER_FAILED = 32; private static final int MSG_HANDOVER_COMPLETE = 33; private static final int MSG_DEFLECT = 34; + private static final int MSG_CREATE_CONFERENCE = 35; + private static final int MSG_CREATE_CONFERENCE_COMPLETE = 36; + private static final int MSG_CREATE_CONFERENCE_FAILED = 37; private static Connection sNullConnection; @@ -291,6 +297,63 @@ public abstract class ConnectionService extends Service { } @Override + public void createConference( + PhoneAccountHandle connectionManagerPhoneAccount, + String id, + ConnectionRequest request, + boolean isIncoming, + boolean isUnknown, + Session.Info sessionInfo) { + Log.startSession(sessionInfo, SESSION_CREATE_CONF); + try { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = connectionManagerPhoneAccount; + args.arg2 = id; + args.arg3 = request; + args.arg4 = Log.createSubsession(); + args.argi1 = isIncoming ? 1 : 0; + args.argi2 = isUnknown ? 1 : 0; + mHandler.obtainMessage(MSG_CREATE_CONFERENCE, args).sendToTarget(); + } finally { + Log.endSession(); + } + } + + @Override + public void createConferenceComplete(String id, Session.Info sessionInfo) { + Log.startSession(sessionInfo, SESSION_CREATE_CONF_COMPLETE); + try { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = id; + args.arg2 = Log.createSubsession(); + mHandler.obtainMessage(MSG_CREATE_CONFERENCE_COMPLETE, args).sendToTarget(); + } finally { + Log.endSession(); + } + } + + @Override + public void createConferenceFailed( + PhoneAccountHandle connectionManagerPhoneAccount, + String callId, + ConnectionRequest request, + boolean isIncoming, + Session.Info sessionInfo) { + Log.startSession(sessionInfo, SESSION_CREATE_CONF_FAILED); + try { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId; + args.arg2 = request; + args.arg3 = Log.createSubsession(); + args.arg4 = connectionManagerPhoneAccount; + args.argi1 = isIncoming ? 1 : 0; + mHandler.obtainMessage(MSG_CREATE_CONFERENCE_FAILED, args).sendToTarget(); + } finally { + Log.endSession(); + } + } + + @Override public void handoverFailed(String callId, ConnectionRequest request, int reason, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_HANDOVER_FAILED); @@ -802,6 +865,106 @@ public abstract class ConnectionService extends Service { } break; } + case MSG_CREATE_CONFERENCE: { + SomeArgs args = (SomeArgs) msg.obj; + Log.continueSession((Session) args.arg4, SESSION_HANDLER + SESSION_CREATE_CONN); + try { + final PhoneAccountHandle connectionManagerPhoneAccount = + (PhoneAccountHandle) args.arg1; + final String id = (String) args.arg2; + final ConnectionRequest request = (ConnectionRequest) args.arg3; + final boolean isIncoming = args.argi1 == 1; + final boolean isUnknown = args.argi2 == 1; + if (!mAreAccountsInitialized) { + Log.d(this, "Enqueueing pre-initconference request %s", id); + mPreInitializationConnectionRequests.add( + new android.telecom.Logging.Runnable( + SESSION_HANDLER + SESSION_CREATE_CONF + ".pIConfR", + null /*lock*/) { + @Override + public void loggedRun() { + createConference(connectionManagerPhoneAccount, + id, + request, + isIncoming, + isUnknown); + } + }.prepare()); + } else { + createConference(connectionManagerPhoneAccount, + id, + request, + isIncoming, + isUnknown); + } + } finally { + args.recycle(); + Log.endSession(); + } + break; + } + case MSG_CREATE_CONFERENCE_COMPLETE: { + SomeArgs args = (SomeArgs) msg.obj; + Log.continueSession((Session) args.arg2, + SESSION_HANDLER + SESSION_CREATE_CONN_COMPLETE); + try { + final String id = (String) args.arg1; + if (!mAreAccountsInitialized) { + Log.d(this, "Enqueueing pre-init conference request %s", id); + mPreInitializationConnectionRequests.add( + new android.telecom.Logging.Runnable( + SESSION_HANDLER + SESSION_CREATE_CONF_COMPLETE + + ".pIConfR", + null /*lock*/) { + @Override + public void loggedRun() { + notifyCreateConferenceComplete(id); + } + }.prepare()); + } else { + notifyCreateConferenceComplete(id); + } + } finally { + args.recycle(); + Log.endSession(); + } + break; + } + case MSG_CREATE_CONFERENCE_FAILED: { + SomeArgs args = (SomeArgs) msg.obj; + Log.continueSession((Session) args.arg3, SESSION_HANDLER + + SESSION_CREATE_CONN_FAILED); + try { + final String id = (String) args.arg1; + final ConnectionRequest request = (ConnectionRequest) args.arg2; + final boolean isIncoming = args.argi1 == 1; + final PhoneAccountHandle connectionMgrPhoneAccount = + (PhoneAccountHandle) args.arg4; + if (!mAreAccountsInitialized) { + Log.d(this, "Enqueueing pre-init conference request %s", id); + mPreInitializationConnectionRequests.add( + new android.telecom.Logging.Runnable( + SESSION_HANDLER + SESSION_CREATE_CONF_FAILED + + ".pIConfR", + null /*lock*/) { + @Override + public void loggedRun() { + createConferenceFailed(connectionMgrPhoneAccount, id, + request, isIncoming); + } + }.prepare()); + } else { + Log.i(this, "createConferenceFailed %s", id); + createConferenceFailed(connectionMgrPhoneAccount, id, request, + isIncoming); + } + } finally { + args.recycle(); + Log.endSession(); + } + break; + } + case MSG_HANDOVER_FAILED: { SomeArgs args = (SomeArgs) msg.obj; Log.continueSession((Session) args.arg3, SESSION_HANDLER + @@ -1162,6 +1325,12 @@ public abstract class ConnectionService extends Service { public void onStateChanged(Conference conference, int oldState, int newState) { String id = mIdByConference.get(conference); switch (newState) { + case Connection.STATE_RINGING: + mAdapter.setRinging(id); + break; + case Connection.STATE_DIALING: + mAdapter.setDialing(id); + break; case Connection.STATE_ACTIVE: mAdapter.setActive(id); break; @@ -1292,6 +1461,13 @@ public abstract class ConnectionService extends Service { mAdapter.onConnectionEvent(id, event, extras); } } + + @Override + public void onRingbackRequested(Conference c, boolean ringback) { + String id = mIdByConference.get(c); + Log.d(this, "Adapter conference onRingback %b", ringback); + mAdapter.setRingbackRequested(id, ringback); + } }; private final Connection.Listener mConnectionListener = new Connection.Listener() { @@ -1534,6 +1710,70 @@ public abstract class ConnectionService extends Service { return super.onUnbind(intent); } + + /** + * This can be used by telecom to either create a new outgoing conference call or attach + * to an existing incoming conference call. In either case, telecom will cycle through a + * set of services and call createConference until a connection service cancels the process + * or completes it successfully. + */ + private void createConference( + final PhoneAccountHandle callManagerAccount, + final String callId, + final ConnectionRequest request, + boolean isIncoming, + boolean isUnknown) { + + Conference conference = null; + conference = isIncoming ? onCreateIncomingConference(callManagerAccount, request) + : onCreateOutgoingConference(callManagerAccount, request); + + Log.d(this, "createConference, conference: %s", conference); + if (conference == null) { + Log.i(this, "createConference, implementation returned null conference."); + conference = Conference.createFailedConference( + new DisconnectCause(DisconnectCause.ERROR, "IMPL_RETURNED_NULL_CONFERENCE"), + request.getAccountHandle()); + } + if (conference.getExtras() != null) { + conference.getExtras().putString(Connection.EXTRA_ORIGINAL_CONNECTION_ID, callId); + } + mConferenceById.put(callId, conference); + mIdByConference.put(conference, callId); + conference.addListener(mConferenceListener); + ParcelableConference parcelableConference = new ParcelableConference( + request.getAccountHandle(), + conference.getState(), + conference.getConnectionCapabilities(), + conference.getConnectionProperties(), + Collections.<String>emptyList(), //connectionIds + conference.getVideoProvider() == null ? + null : conference.getVideoProvider().getInterface(), + conference.getVideoState(), + conference.getConnectTimeMillis(), + conference.getConnectionStartElapsedRealTime(), + conference.getStatusHints(), + conference.getExtras(), + conference.getAddress(), + conference.getAddressPresentation(), + conference.getCallerDisplayName(), + conference.getCallerDisplayNamePresentation(), + conference.getDisconnectCause(), + conference.isRingbackRequested()); + if (conference.getState() != Connection.STATE_DISCONNECTED) { + conference.setTelecomCallId(callId); + mAdapter.setVideoProvider(callId, conference.getVideoProvider()); + mAdapter.setVideoState(callId, conference.getVideoState()); + onConferenceAdded(conference); + } + + Log.d(this, "createConference, calling handleCreateConferenceSuccessful %s", callId); + mAdapter.handleCreateConferenceComplete( + callId, + request, + parcelableConference); + } + /** * This can be used by telecom to either create a new outgoing call or attach to an existing * incoming call. In either case, telecom will cycle through a set of services and call @@ -1645,6 +1885,18 @@ public abstract class ConnectionService extends Service { } } + private void createConferenceFailed(final PhoneAccountHandle callManagerAccount, + final String callId, final ConnectionRequest request, + boolean isIncoming) { + + Log.i(this, "createConferenceFailed %s", callId); + if (isIncoming) { + onCreateIncomingConferenceFailed(callManagerAccount, request); + } else { + onCreateOutgoingConferenceFailed(callManagerAccount, request); + } + } + private void handoverFailed(final String callId, final ConnectionRequest request, int reason) { @@ -1669,6 +1921,24 @@ public abstract class ConnectionService extends Service { "notifyCreateConnectionComplete")); } + /** + * Called by Telecom when the creation of a new Conference has completed and it is now added + * to Telecom. + * @param callId The ID of the connection. + */ + private void notifyCreateConferenceComplete(final String callId) { + Log.i(this, "notifyCreateConferenceComplete %s", callId); + if (callId == null) { + // This could happen if the conference fails quickly and is removed from the + // ConnectionService before Telecom sends the create conference complete callback. + Log.w(this, "notifyCreateConferenceComplete: callId is null."); + return; + } + onCreateConferenceComplete(findConferenceForAction(callId, + "notifyCreateConferenceComplete")); + } + + private void abort(String callId) { Log.d(this, "abort %s", callId); findConnectionForAction(callId, "abort").onAbort(); @@ -1676,12 +1946,20 @@ public abstract class ConnectionService extends Service { private void answerVideo(String callId, int videoState) { Log.d(this, "answerVideo %s", callId); - findConnectionForAction(callId, "answer").onAnswer(videoState); + if (mConnectionById.containsKey(callId)) { + findConnectionForAction(callId, "answer").onAnswer(videoState); + } else { + findConferenceForAction(callId, "answer").onAnswer(videoState); + } } private void answer(String callId) { Log.d(this, "answer %s", callId); - findConnectionForAction(callId, "answer").onAnswer(); + if (mConnectionById.containsKey(callId)) { + findConnectionForAction(callId, "answer").onAnswer(); + } else { + findConferenceForAction(callId, "answer").onAnswer(); + } } private void deflect(String callId, Uri address) { @@ -1691,7 +1969,11 @@ public abstract class ConnectionService extends Service { private void reject(String callId) { Log.d(this, "reject %s", callId); - findConnectionForAction(callId, "reject").onReject(); + if (mConnectionById.containsKey(callId)) { + findConnectionForAction(callId, "reject").onReject(); + } else { + findConferenceForAction(callId, "reject").onReject(); + } } private void reject(String callId, String rejectWithMessage) { @@ -2198,6 +2480,21 @@ public abstract class ConnectionService extends Service { ConnectionRequest request) { return null; } + /** + * Create a {@code Connection} given an incoming request. This is used to attach to existing + * incoming conference call. + * + * @param connectionManagerPhoneAccount See description at + * {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}. + * @param request Details about the incoming call. + * @return The {@code Connection} object to satisfy this call, or {@code null} to + * not handle the call. + */ + public @Nullable Conference onCreateIncomingConference( + @Nullable PhoneAccountHandle connectionManagerPhoneAccount, + @Nullable ConnectionRequest request) { + return null; + } /** * Called after the {@link Connection} returned by @@ -2212,6 +2509,19 @@ public abstract class ConnectionService extends Service { } /** + * Called after the {@link Conference} returned by + * {@link #onCreateIncomingConference(PhoneAccountHandle, ConnectionRequest)} + * or {@link #onCreateOutgoingConference(PhoneAccountHandle, ConnectionRequest)} has been + * added to the {@link ConnectionService} and sent to Telecom. + * + * @param conference the {@link Conference}. + * @hide + */ + public void onCreateConferenceComplete(Conference conference) { + } + + + /** * Called by Telecom to inform the {@link ConnectionService} that its request to create a new * incoming {@link Connection} was denied. * <p> @@ -2250,6 +2560,47 @@ public abstract class ConnectionService extends Service { } /** + * Called by Telecom to inform the {@link ConnectionService} that its request to create a new + * incoming {@link Conference} was denied. + * <p> + * Used when a self-managed {@link ConnectionService} attempts to create a new incoming + * {@link Conference}, but Telecom has determined that the call cannot be allowed at this time. + * The {@link ConnectionService} is responsible for silently rejecting the new incoming + * {@link Conference}. + * <p> + * See {@link TelecomManager#isIncomingCallPermitted(PhoneAccountHandle)} for more information. + * + * @param connectionManagerPhoneAccount See description at + * {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}. + * @param request The incoming connection request. + */ + public void onCreateIncomingConferenceFailed( + @Nullable PhoneAccountHandle connectionManagerPhoneAccount, + @Nullable ConnectionRequest request) { + } + + /** + * Called by Telecom to inform the {@link ConnectionService} that its request to create a new + * outgoing {@link Conference} was denied. + * <p> + * Used when a self-managed {@link ConnectionService} attempts to create a new outgoing + * {@link Conference}, but Telecom has determined that the call cannot be placed at this time. + * The {@link ConnectionService} is responisible for informing the user that the + * {@link Conference} cannot be made at this time. + * <p> + * See {@link TelecomManager#isOutgoingCallPermitted(PhoneAccountHandle)} for more information. + * + * @param connectionManagerPhoneAccount See description at + * {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}. + * @param request The outgoing connection request. + */ + public void onCreateOutgoingConferenceFailed( + @Nullable PhoneAccountHandle connectionManagerPhoneAccount, + @Nullable ConnectionRequest request) { + } + + + /** * Trigger recalculate functinality for conference calls. This is used when a Telephony * Connection is part of a conference controller but is not yet added to Connection * Service and hence cannot be added to the conference call. @@ -2289,6 +2640,36 @@ public abstract class ConnectionService extends Service { } /** + * Create a {@code Conference} given an outgoing request. This is used to initiate new + * outgoing conference call. + * + * @param connectionManagerPhoneAccount The connection manager account to use for managing + * this call. + * <p> + * If this parameter is not {@code null}, it means that this {@code ConnectionService} + * has registered one or more {@code PhoneAccount}s having + * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}. This parameter will contain + * one of these {@code PhoneAccount}s, while the {@code request} will contain another + * (usually but not always distinct) {@code PhoneAccount} to be used for actually + * making the connection. + * <p> + * If this parameter is {@code null}, it means that this {@code ConnectionService} is + * being asked to make a direct connection. The + * {@link ConnectionRequest#getAccountHandle()} of parameter {@code request} will be + * a {@code PhoneAccount} registered by this {@code ConnectionService} to use for + * making the connection. + * @param request Details about the outgoing call. + * @return The {@code Conference} object to satisfy this call, or the result of an invocation + * of {@link Connection#createFailedConnection(DisconnectCause)} to not handle the call. + */ + public @Nullable Conference onCreateOutgoingConference( + @Nullable PhoneAccountHandle connectionManagerPhoneAccount, + @Nullable ConnectionRequest request) { + return null; + } + + + /** * Called by Telecom to request that a {@link ConnectionService} creates an instance of an * outgoing handover {@link Connection}. * <p> diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapter.java b/telecomm/java/android/telecom/ConnectionServiceAdapter.java index 04e930ccd954..8f273233044e 100644 --- a/telecomm/java/android/telecom/ConnectionServiceAdapter.java +++ b/telecomm/java/android/telecom/ConnectionServiceAdapter.java @@ -100,6 +100,19 @@ final class ConnectionServiceAdapter implements DeathRecipient { } } + void handleCreateConferenceComplete( + String id, + ConnectionRequest request, + ParcelableConference conference) { + for (IConnectionServiceAdapter adapter : mAdapters) { + try { + adapter.handleCreateConferenceComplete(id, request, conference, + Log.getExternalSession()); + } catch (RemoteException e) { + } + } + } + /** * Sets a call's state to active (e.g., an ongoing call where two parties can actively * communicate). diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java index 60b2172fdeca..79ad51b92b81 100644 --- a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java +++ b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java @@ -75,6 +75,7 @@ final class ConnectionServiceAdapterServant { private static final int MSG_SET_PHONE_ACCOUNT_CHANGED = 34; private static final int MSG_CONNECTION_SERVICE_FOCUS_RELEASED = 35; private static final int MSG_SET_CONFERENCE_STATE = 36; + private static final int MSG_HANDLE_CREATE_CONFERENCE_COMPLETE = 37; private final IConnectionServiceAdapter mDelegate; @@ -103,6 +104,19 @@ final class ConnectionServiceAdapterServant { } break; } + case MSG_HANDLE_CREATE_CONFERENCE_COMPLETE: { + SomeArgs args = (SomeArgs) msg.obj; + try { + mDelegate.handleCreateConferenceComplete( + (String) args.arg1, + (ConnectionRequest) args.arg2, + (ParcelableConference) args.arg3, + null /*Session.Info*/); + } finally { + args.recycle(); + } + break; + } case MSG_SET_ACTIVE: mDelegate.setActive((String) msg.obj, null /*Session.Info*/); break; @@ -366,6 +380,20 @@ final class ConnectionServiceAdapterServant { } @Override + public void handleCreateConferenceComplete( + String id, + ConnectionRequest request, + ParcelableConference conference, + Session.Info sessionInfo) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = id; + args.arg2 = request; + args.arg3 = conference; + mHandler.obtainMessage(MSG_HANDLE_CREATE_CONFERENCE_COMPLETE, args).sendToTarget(); + } + + + @Override public void setActive(String connectionId, Session.Info sessionInfo) { mHandler.obtainMessage(MSG_SET_ACTIVE, connectionId).sendToTarget(); } diff --git a/telecomm/java/android/telecom/ParcelableConference.java b/telecomm/java/android/telecom/ParcelableConference.java index ede05943772e..90b69a338c7e 100644 --- a/telecomm/java/android/telecom/ParcelableConference.java +++ b/telecomm/java/android/telecom/ParcelableConference.java @@ -47,6 +47,34 @@ public final class ParcelableConference implements Parcelable { private final int mAddressPresentation; private final String mCallerDisplayName; private final int mCallerDisplayNamePresentation; + private DisconnectCause mDisconnectCause; + private boolean mRingbackRequested; + + public ParcelableConference( + PhoneAccountHandle phoneAccount, + int state, + int connectionCapabilities, + int connectionProperties, + List<String> connectionIds, + IVideoProvider videoProvider, + int videoState, + long connectTimeMillis, + long connectElapsedTimeMillis, + StatusHints statusHints, + Bundle extras, + Uri address, + int addressPresentation, + String callerDisplayName, + int callerDisplayNamePresentation, + DisconnectCause disconnectCause, + boolean ringbackRequested) { + this(phoneAccount, state, connectionCapabilities, connectionProperties, connectionIds, + videoProvider, videoState, connectTimeMillis, connectElapsedTimeMillis, + statusHints, extras, address, addressPresentation, callerDisplayName, + callerDisplayNamePresentation); + mDisconnectCause = disconnectCause; + mRingbackRequested = ringbackRequested; + } public ParcelableConference( PhoneAccountHandle phoneAccount, @@ -79,6 +107,8 @@ public final class ParcelableConference implements Parcelable { mAddressPresentation = addressPresentation; mCallerDisplayName = callerDisplayName; mCallerDisplayNamePresentation = callerDisplayNamePresentation; + mDisconnectCause = null; + mRingbackRequested = false; } @Override @@ -100,6 +130,10 @@ public final class ParcelableConference implements Parcelable { .append(mVideoState) .append(", VideoProvider: ") .append(mVideoProvider) + .append(", isRingbackRequested: ") + .append(mRingbackRequested) + .append(", disconnectCause: ") + .append(mDisconnectCause) .toString(); } @@ -151,6 +185,13 @@ public final class ParcelableConference implements Parcelable { return mAddress; } + public final DisconnectCause getDisconnectCause() { + return mDisconnectCause; + } + + public boolean isRingbackRequested() { + return mRingbackRequested; + } public int getHandlePresentation() { return mAddressPresentation; } @@ -177,11 +218,14 @@ public final class ParcelableConference implements Parcelable { int addressPresentation = source.readInt(); String callerDisplayName = source.readString(); int callerDisplayNamePresentation = source.readInt(); + DisconnectCause disconnectCause = source.readParcelable(classLoader); + boolean isRingbackRequested = source.readInt() == 1; return new ParcelableConference(phoneAccount, state, capabilities, properties, connectionIds, videoCallProvider, videoState, connectTimeMillis, connectElapsedTimeMillis, statusHints, extras, address, addressPresentation, - callerDisplayName, callerDisplayNamePresentation); + callerDisplayName, callerDisplayNamePresentation, disconnectCause, + isRingbackRequested); } @Override @@ -215,5 +259,7 @@ public final class ParcelableConference implements Parcelable { destination.writeInt(mAddressPresentation); destination.writeString(mCallerDisplayName); destination.writeInt(mCallerDisplayNamePresentation); + destination.writeParcelable(mDisconnectCause, 0); + destination.writeInt(mRingbackRequested ? 1 : 0); } } diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java index bb858cb0761b..abb210f13376 100644 --- a/telecomm/java/android/telecom/PhoneAccount.java +++ b/telecomm/java/android/telecom/PhoneAccount.java @@ -331,7 +331,17 @@ public final class PhoneAccount implements Parcelable { */ public static final int CAPABILITY_EMERGENCY_PREFERRED = 0x2000; - /* NEXT CAPABILITY: 0x4000 */ + /** + * An adhoc conference call is established by providing a list of addresses to + * {@code TelecomManager#startConference(List<Uri>, int videoState)} where the + * {@link ConnectionService} is responsible for connecting all indicated participants + * to a conference simultaneously. + * This is in contrast to conferences formed by merging calls together (e.g. using + * {@link android.telecom.Call#mergeConference()}). + */ + public static final int CAPABILITY_ADHOC_CONFERENCE_CALLING = 0x4000; + + /* NEXT CAPABILITY: 0x8000 */ /** * URI scheme for telephone number URIs. @@ -1054,6 +1064,9 @@ public final class PhoneAccount implements Parcelable { if (hasCapabilities(CAPABILITY_RTT)) { sb.append("Rtt"); } + if (hasCapabilities(CAPABILITY_ADHOC_CONFERENCE_CALLING)) { + sb.append("AdhocConf"); + } return sb.toString(); } diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java index 1e73bd61d68e..76640e036eeb 100644 --- a/telecomm/java/android/telecom/RemoteConnectionService.java +++ b/telecomm/java/android/telecom/RemoteConnectionService.java @@ -101,6 +101,14 @@ final class RemoteConnectionService { } @Override + public void handleCreateConferenceComplete( + String id, + ConnectionRequest request, + ParcelableConference parcel, + Session.Info info) { + } + + @Override public void setActive(String callId, Session.Info sessionInfo) { if (mConnectionById.containsKey(callId)) { findConnectionForAction(callId, "setActive") diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index c3fb51076bba..49b74c6061f4 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -1810,6 +1810,45 @@ public class TelecomManager { } /** + * Registers a new incoming conference. A {@link ConnectionService} should invoke this method + * when it has an incoming conference. For managed {@link ConnectionService}s, the specified + * {@link PhoneAccountHandle} must have been registered with {@link #registerPhoneAccount} and + * the user must have enabled the corresponding {@link PhoneAccount}. This can be checked using + * {@link #getPhoneAccount}. Self-managed {@link ConnectionService}s must have + * {@link android.Manifest.permission#MANAGE_OWN_CALLS} to add a new incoming call. + * <p> + * The incoming conference you are adding is assumed to have a video state of + * {@link VideoProfile#STATE_AUDIO_ONLY}, unless the extra value + * {@link #EXTRA_INCOMING_VIDEO_STATE} is specified. + * <p> + * Once invoked, this method will cause the system to bind to the {@link ConnectionService} + * associated with the {@link PhoneAccountHandle} and request additional information about the + * call (See {@link ConnectionService#onCreateIncomingConference}) before starting the incoming + * call UI. + * <p> + * For a managed {@link ConnectionService}, a {@link SecurityException} will be thrown if either + * the {@link PhoneAccountHandle} does not correspond to a registered {@link PhoneAccount} or + * the associated {@link PhoneAccount} is not currently enabled by the user. + * + * @param phoneAccount A {@link PhoneAccountHandle} registered with + * {@link #registerPhoneAccount}. + * @param extras A bundle that will be passed through to + * {@link ConnectionService#onCreateIncomingConference}. + */ + + public void addNewIncomingConference(@NonNull PhoneAccountHandle phoneAccount, + @NonNull Bundle extras) { + try { + if (isServiceConnected()) { + getTelecomService().addNewIncomingConference( + phoneAccount, extras == null ? new Bundle() : extras); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException adding a new incoming conference: " + phoneAccount, e); + } + } + + /** * Registers a new unknown call with Telecom. This can only be called by the system Telephony * service. This is invoked when Telephony detects a new unknown connection that was neither * a new incoming call, nor an user-initiated outgoing call. @@ -2014,6 +2053,42 @@ public class TelecomManager { } } + + /** + * Place a new conference call with the provided participants using the system telecom service + * This method doesn't support placing of emergency calls. + * + * An adhoc conference call is established by providing a list of addresses to + * {@code TelecomManager#startConference(List<Uri>, int videoState)} where the + * {@link ConnectionService} is responsible for connecting all indicated participants + * to a conference simultaneously. + * This is in contrast to conferences formed by merging calls together (e.g. using + * {@link android.telecom.Call#mergeConference()}). + * + * The following keys are supported in the supplied extras. + * <ul> + * <li>{@link #EXTRA_PHONE_ACCOUNT_HANDLE}</li> + * <li>{@link #EXTRA_START_CALL_WITH_SPEAKERPHONE}</li> + * <li>{@link #EXTRA_START_CALL_WITH_VIDEO_STATE}</li> + * </ul> + * + * @param participants List of participants to start conference with + * @param extras Bundle of extras to use with the call + */ + @RequiresPermission(android.Manifest.permission.CALL_PHONE) + public void startConference(@NonNull List<Uri> participants, + @NonNull Bundle extras) { + ITelecomService service = getTelecomService(); + if (service != null) { + try { + service.startConference(participants, extras, + mContext.getOpPackageName()); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecomService#placeCall", e); + } + } + } + /** * Enables and disables specified phone account. * diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl index e35093c9656a..96f2483f32f9 100644 --- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl +++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl @@ -53,6 +53,20 @@ oneway interface IConnectionService { void createConnectionFailed(in PhoneAccountHandle connectionManagerPhoneAccount, String callId, in ConnectionRequest request, boolean isIncoming, in Session.Info sessionInfo); + void createConference( + in PhoneAccountHandle connectionManagerPhoneAccount, + String callId, + in ConnectionRequest request, + boolean isIncoming, + boolean isUnknown, + in Session.Info sessionInfo); + + void createConferenceComplete(String callId, in Session.Info sessionInfo); + + void createConferenceFailed(in PhoneAccountHandle connectionManagerPhoneAccount, String callId, + in ConnectionRequest request, boolean isIncoming, in Session.Info sessionInfo); + + void abort(String callId, in Session.Info sessionInfo); void answerVideo(String callId, int videoState, in Session.Info sessionInfo); diff --git a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl index 9cf098c75177..4f63e08abce6 100644 --- a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl +++ b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl @@ -44,6 +44,12 @@ oneway interface IConnectionServiceAdapter { in ParcelableConnection connection, in Session.Info sessionInfo); + void handleCreateConferenceComplete( + String callId, + in ConnectionRequest request, + in ParcelableConference connection, + in Session.Info sessionInfo); + void setActive(String callId, in Session.Info sessionInfo); void setRinging(String callId, in Session.Info sessionInfo); diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index c54da6b4d527..285cf43cd3a1 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -246,11 +246,22 @@ interface ITelecomService { void addNewIncomingCall(in PhoneAccountHandle phoneAccount, in Bundle extras); /** + * @see TelecomServiceImpl#addNewIncomingConference + */ + void addNewIncomingConference(in PhoneAccountHandle phoneAccount, in Bundle extras); + + /** * @see TelecomServiceImpl#addNewUnknownCall */ void addNewUnknownCall(in PhoneAccountHandle phoneAccount, in Bundle extras); /** + * @see TelecomServiceImpl#startConference + */ + void startConference(in List<Uri> participants, in Bundle extras, + String callingPackage); + + /** * @see TelecomServiceImpl#placeCall */ void placeCall(in Uri handle, in Bundle extras, String callingPackage, String callingFeatureId); diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java index 9b8282806c3c..ffb3cb151ae4 100644 --- a/telephony/common/com/android/internal/telephony/SmsApplication.java +++ b/telephony/common/com/android/internal/telephony/SmsApplication.java @@ -205,7 +205,7 @@ public final class SmsApplication { < android.os.Process.FIRST_APPLICATION_UID) { return contextUserId; } else { - return UserHandle.getUserId(callingUid); + return UserHandle.getUserHandleForUid(callingUid).getIdentifier(); } } @@ -811,10 +811,10 @@ public final class SmsApplication { // This should never happen in prod -- unit tests will put the receiver into a // unusual state where the pending result is null, which produces a NPE when calling // getSendingUserId. Just pretend like it's the system user for testing. - userId = UserHandle.USER_SYSTEM; + userId = UserHandle.SYSTEM.getIdentifier(); } Context userContext = mContext; - if (userId != UserHandle.USER_SYSTEM) { + if (userId != UserHandle.SYSTEM.getIdentifier()) { try { userContext = mContext.createPackageContextAsUser(mContext.getPackageName(), 0, UserHandle.of(userId)); diff --git a/telephony/java/android/service/euicc/EuiccService.java b/telephony/java/android/service/euicc/EuiccService.java index ef11f469d9a0..93155865c166 100644 --- a/telephony/java/android/service/euicc/EuiccService.java +++ b/telephony/java/android/service/euicc/EuiccService.java @@ -31,7 +31,9 @@ import android.os.RemoteException; import android.telephony.TelephonyManager; import android.telephony.euicc.DownloadableSubscription; import android.telephony.euicc.EuiccInfo; +import android.telephony.euicc.EuiccManager; import android.telephony.euicc.EuiccManager.OtaStatus; +import android.text.TextUtils; import android.util.Log; import java.io.PrintWriter; @@ -311,6 +313,65 @@ public abstract class EuiccService extends Service { mStubWrapper = new IEuiccServiceWrapper(); } + /** + * Given a SubjectCode[5.2.6.1] and ReasonCode[5.2.6.2] from GSMA (SGP.22 v2.2), encode it to + * the format described in + * {@link android.telephony.euicc.EuiccManager#OPERATION_SMDX_SUBJECT_REASON_CODE} + * + * @param subjectCode SubjectCode[5.2.6.1] from GSMA (SGP.22 v2.2) + * @param reasonCode ReasonCode[5.2.6.2] from GSMA (SGP.22 v2.2) + * @return encoded error code described in + * {@link android.telephony.euicc.EuiccManager#OPERATION_SMDX_SUBJECT_REASON_CODE} + * @throws NumberFormatException when the Subject/Reason code contains non digits + * @throws IllegalArgumentException when Subject/Reason code is null/empty + * @throws UnsupportedOperationException when sections has more than four layers (e.g 5.8.1.2) + * or when an number is bigger than 15 + */ + public int encodeSmdxSubjectAndReasonCode(@Nullable String subjectCode, + @Nullable String reasonCode) + throws NumberFormatException, IllegalArgumentException, UnsupportedOperationException { + final int maxSupportedSection = 3; + final int maxSupportedDigit = 15; + final int bitsPerSection = 4; + + if (TextUtils.isEmpty(subjectCode) || TextUtils.isEmpty(reasonCode)) { + throw new IllegalArgumentException("SubjectCode/ReasonCode is empty"); + } + + final String[] subjectCodeToken = subjectCode.split("\\."); + final String[] reasonCodeToken = reasonCode.split("\\."); + + if (subjectCodeToken.length > maxSupportedSection + || reasonCodeToken.length > maxSupportedSection) { + throw new UnsupportedOperationException("Only three nested layer is supported."); + } + + int result = EuiccManager.OPERATION_SMDX_SUBJECT_REASON_CODE; + + // Pad the 0s needed for subject code + result = result << (maxSupportedSection - subjectCodeToken.length) * bitsPerSection; + + for (String digitString : subjectCodeToken) { + int num = Integer.parseInt(digitString); + if (num > maxSupportedDigit) { + throw new UnsupportedOperationException("SubjectCode exceeds " + maxSupportedDigit); + } + result = (result << bitsPerSection) + num; + } + + // Pad the 0s needed for reason code + result = result << (maxSupportedSection - reasonCodeToken.length) * bitsPerSection; + for (String digitString : reasonCodeToken) { + int num = Integer.parseInt(digitString); + if (num > maxSupportedDigit) { + throw new UnsupportedOperationException("ReasonCode exceeds " + maxSupportedDigit); + } + result = (result << bitsPerSection) + num; + } + + return result; + } + @Override @CallSuper public void onCreate() { diff --git a/telephony/java/android/telephony/AccessNetworkConstants.java b/telephony/java/android/telephony/AccessNetworkConstants.java index b1d647fec0c5..610eef80c571 100644 --- a/telephony/java/android/telephony/AccessNetworkConstants.java +++ b/telephony/java/android/telephony/AccessNetworkConstants.java @@ -317,6 +317,159 @@ public final class AccessNetworkConstants { public static final int BAND_260 = 260; public static final int BAND_261 = 261; + /** + * NR Bands + * + * @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"BAND_"}, + value = {BAND_1, + BAND_2, + BAND_3, + BAND_5, + BAND_7, + BAND_8, + BAND_12, + BAND_14, + BAND_18, + BAND_20, + BAND_25, + BAND_28, + BAND_29, + BAND_30, + BAND_34, + BAND_38, + BAND_39, + BAND_40, + BAND_41, + BAND_48, + BAND_50, + BAND_51, + BAND_65, + BAND_66, + BAND_70, + BAND_71, + BAND_74, + BAND_75, + BAND_76, + BAND_77, + BAND_78, + BAND_79, + BAND_80, + BAND_81, + BAND_82, + BAND_83, + BAND_84, + BAND_86, + BAND_90, + BAND_257, + BAND_258, + BAND_260, + BAND_261}) + public @interface NgranBand {} + + /** + * Unknown NR frequency. + * + * @hide + */ + @SystemApi + @TestApi + public static final int FREQUENCY_RANGE_GROUP_UNKNOWN = 0; + + /** + * NR frequency group 1 defined in 3GPP TS 38.101-1 table 5.2-1 + * + * @hide + */ + @SystemApi + @TestApi + public static final int FREQUENCY_RANGE_GROUP_1 = 1; + + /** + * NR frequency group 2 defined in 3GPP TS 38.101-2 table 5.2-1 + * + * @hide + */ + @SystemApi + @TestApi + public static final int FREQUENCY_RANGE_GROUP_2 = 2; + + /** + * Radio frequency range group + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"FREQUENCY_RANGE_GROUP_"}, + value = { + FREQUENCY_RANGE_GROUP_UNKNOWN, + FREQUENCY_RANGE_GROUP_1, + FREQUENCY_RANGE_GROUP_2}) + public @interface FrequencyRangeGroup {} + + /** + * Get frequency range group + * + * @param band NR band + * @return The frequency range group + * + * @hide + */ + @SystemApi + @TestApi + public static @FrequencyRangeGroup int getFrequencyRangeGroup(@NgranBand int band) { + switch (band) { + case BAND_1: + case BAND_2: + case BAND_3: + case BAND_5: + case BAND_7: + case BAND_8: + case BAND_12: + case BAND_14: + case BAND_18: + case BAND_20: + case BAND_25: + case BAND_28: + case BAND_29: + case BAND_30: + case BAND_34: + case BAND_38: + case BAND_39: + case BAND_40: + case BAND_41: + case BAND_48: + case BAND_50: + case BAND_51: + case BAND_65: + case BAND_66: + case BAND_70: + case BAND_71: + case BAND_74: + case BAND_75: + case BAND_76: + case BAND_77: + case BAND_78: + case BAND_79: + case BAND_80: + case BAND_81: + case BAND_82: + case BAND_83: + case BAND_84: + case BAND_86: + case BAND_90: + return FREQUENCY_RANGE_GROUP_1; + case BAND_257: + case BAND_258: + case BAND_260: + case BAND_261: + return FREQUENCY_RANGE_GROUP_2; + default: + return FREQUENCY_RANGE_GROUP_UNKNOWN; + } + }; + /** @hide */ private NgranBands() {} } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 7abad72c5f90..b30f5868cc63 100755 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1080,6 +1080,14 @@ public class CarrierConfigManager { public static final String KEY_IGNORE_RTT_MODE_SETTING_BOOL = "ignore_rtt_mode_setting_bool"; + + /** + * Determines whether adhoc conference calls are supported by a carrier. When {@code true}, + * adhoc conference calling is supported, {@code false otherwise}. + */ + public static final String KEY_SUPPORT_ADHOC_CONFERENCE_CALLS_BOOL = + "support_adhoc_conference_calls_bool"; + /** * Determines whether conference calls are supported by a carrier. When {@code true}, * conference calling is supported, {@code false otherwise}. @@ -1446,6 +1454,50 @@ public class CarrierConfigManager { "apn_settings_default_apn_types_string_array"; /** + * Configs used for APN setup. + */ + public static final class Apn { + /** Prefix of all Apn.KEY_* constants. */ + public static final String KEY_PREFIX = "apn."; + + /** IPv4 internet protocol */ + public static final String PROTOCOL_IPV4 = "IP"; + /** IPv6 internet protocol */ + public static final String PROTOCOL_IPV6 = "IPV6"; + /** IPv4 or IPv6 internet protocol */ + public static final String PROTOCOL_IPV4V6 = "IPV4V6"; + + /** + * Default value of APN protocol field if not specified by user when adding/modifying + * an APN. + * + * Available options are: {@link #PROTOCOL_IPV4}, {@link #PROTOCOL_IPV6}, + * {@link #PROTOCOL_IPV4V6} + */ + public static final String KEY_SETTINGS_DEFAULT_PROTOCOL_STRING = + KEY_PREFIX + "settings_default_protocol_string"; + + /** + * Default value of APN roaming protocol field if not specified by user when + * adding/modifying an APN. + * + * Available options are: {@link #PROTOCOL_IPV4}, {@link #PROTOCOL_IPV6}, + * {@link #PROTOCOL_IPV4V6} + */ + public static final String KEY_SETTINGS_DEFAULT_ROAMING_PROTOCOL_STRING = + KEY_PREFIX + "settings_default_roaming_protocol_string"; + + private Apn() {} + + private static PersistableBundle getDefaults() { + PersistableBundle defaults = new PersistableBundle(); + defaults.putString(KEY_SETTINGS_DEFAULT_PROTOCOL_STRING, ""); + defaults.putString(KEY_SETTINGS_DEFAULT_ROAMING_PROTOCOL_STRING, ""); + return defaults; + } + } + + /** * Boolean indicating if intent for emergency call state changes should be broadcast * @hide */ @@ -2967,7 +3019,6 @@ public class CarrierConfigManager { /** * Controls hysteresis time in milli seconds for which OpportunisticNetworkService * will wait before switching data from opportunistic network to primary network. - * @hide */ public static final String KEY_OPPORTUNISTIC_NETWORK_DATA_SWITCH_EXIT_HYSTERESIS_TIME_LONG = "opportunistic_network_data_switch_exit_hysteresis_time_long"; @@ -2975,14 +3026,12 @@ public class CarrierConfigManager { /** * Controls whether to do ping test before switching data to opportunistic network. * This carrier config is used to disable this feature. - * @hide */ public static final String KEY_PING_TEST_BEFORE_DATA_SWITCH_BOOL = "ping_test_before_data_switch_bool"; /** * Controls time in milliseconds until DcTracker reevaluates 5G connection state. - * @hide */ public static final String KEY_5G_WATCHDOG_TIME_MS_LONG = "5g_watchdog_time_long"; @@ -2991,7 +3040,6 @@ public class CarrierConfigManager { * if primary is out of service. This control only affects system or 1st party app * initiated data switch, but will not override data switch initiated by privileged carrier apps * This carrier config is used to disable this feature. - * @hide */ public static final String KEY_SWITCH_DATA_TO_PRIMARY_IF_PRIMARY_IS_OOS_BOOL = "switch_data_to_primary_if_primary_is_oos_bool"; @@ -3003,7 +3051,6 @@ public class CarrierConfigManager { * #KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSSNR_INT within * #KEY_OPPORTUNISTIC_NETWORK_PING_PONG_TIME_LONG of switching to opportunistic network, * it will be determined as ping pong situation by system app or 1st party app. - * @hide */ public static final String KEY_OPPORTUNISTIC_NETWORK_PING_PONG_TIME_LONG = "opportunistic_network_ping_pong_time_long"; @@ -3017,7 +3064,6 @@ public class CarrierConfigManager { * #KEY_OPPORTUNISTIC_NETWORK_PING_PONG_TIME_LONG. * If ping pong situation continuous #KEY_OPPORTUNISTIC_NETWORK_BACKOFF_TIME_LONG * will be added to previously determined hysteresis time. - * @hide */ public static final String KEY_OPPORTUNISTIC_NETWORK_BACKOFF_TIME_LONG = "opportunistic_network_backoff_time_long"; @@ -3029,7 +3075,6 @@ public class CarrierConfigManager { * continuous ping pong situation or not as described in * #KEY_OPPORTUNISTIC_NETWORK_PING_PONG_TIME_LONG and * #KEY_OPPORTUNISTIC_NETWORK_BACKOFF_TIME_LONG. - * @hide */ public static final String KEY_OPPORTUNISTIC_NETWORK_MAX_BACKOFF_TIME_LONG = "opportunistic_network_max_backoff_time_long"; @@ -3076,7 +3121,6 @@ public class CarrierConfigManager { * validation result, this value defines customized value of how long we wait for validation * success before we fail and revoke the switch. * Time out is in milliseconds. - * @hide */ public static final String KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG = "data_switch_validation_timeout_long"; @@ -3420,6 +3464,14 @@ public class CarrierConfigManager { "prevent_clir_activation_and_deactivation_code_bool"; /** + * Flag specifying whether to show forwarded number on call-in-progress screen. + * When true, forwarded number is shown. + * When false, forwarded number is not shown. + */ + public static final String KEY_SHOW_FORWARDED_NUMBER_BOOL = + "show_forwarded_number_bool"; + + /** * Configs used for epdg tunnel bring up. * * @see <a href="https://tools.ietf.org/html/rfc7296">RFC 7296, Internet Key Exchange @@ -3903,6 +3955,8 @@ public class CarrierConfigManager { sDefaults.putStringArray(KEY_READ_ONLY_APN_TYPES_STRING_ARRAY, new String[] {"dun"}); sDefaults.putStringArray(KEY_READ_ONLY_APN_FIELDS_STRING_ARRAY, null); sDefaults.putStringArray(KEY_APN_SETTINGS_DEFAULT_APN_TYPES_STRING_ARRAY, null); + sDefaults.putAll(Apn.getDefaults()); + sDefaults.putBoolean(KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL, false); sDefaults.putBoolean(KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL, false); sDefaults.putStringArray(KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS, new String[]{ @@ -3948,6 +4002,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_CALL_FORWARDING_MAP_NON_NUMBER_TO_VOICEMAIL_BOOL, false); sDefaults.putBoolean(KEY_IGNORE_RTT_MODE_SETTING_BOOL, false); sDefaults.putInt(KEY_CDMA_3WAYCALL_FLASH_DELAY_INT , 0); + sDefaults.putBoolean(KEY_SUPPORT_ADHOC_CONFERENCE_CALLS_BOOL, false); sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true); sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_CALL_BOOL, true); sDefaults.putBoolean(KEY_SUPPORT_MANAGE_IMS_CONFERENCE_CALL_BOOL, true); @@ -4273,6 +4328,7 @@ public class CarrierConfigManager { // Default wifi configurations. sDefaults.putAll(Wifi.getDefaults()); sDefaults.putBoolean(ENABLE_EAP_METHOD_PREFIX_BOOL, false); + sDefaults.putBoolean(KEY_SHOW_FORWARDED_NUMBER_BOOL, false); sDefaults.putAll(Iwlan.getDefaults()); } diff --git a/telephony/java/android/telephony/CellIdentityGsm.java b/telephony/java/android/telephony/CellIdentityGsm.java index 49f425acead6..dc73cbf735b0 100644 --- a/telephony/java/android/telephony/CellIdentityGsm.java +++ b/telephony/java/android/telephony/CellIdentityGsm.java @@ -23,6 +23,8 @@ import android.os.Parcel; import android.telephony.gsm.GsmCellLocation; import android.text.TextUtils; +import java.util.Collections; +import java.util.List; import java.util.Objects; /** @@ -46,6 +48,9 @@ public final class CellIdentityGsm extends CellIdentity { // 6-bit Base Station Identity Code private final int mBsic; + // a list of additional PLMN-IDs reported for this cell + private final List<String> mAdditionalPlmns; + /** * @hide */ @@ -56,6 +61,7 @@ public final class CellIdentityGsm extends CellIdentity { mCid = CellInfo.UNAVAILABLE; mArfcn = CellInfo.UNAVAILABLE; mBsic = CellInfo.UNAVAILABLE; + mAdditionalPlmns = Collections.emptyList(); } /** @@ -68,35 +74,48 @@ public final class CellIdentityGsm extends CellIdentity { * @param mncStr 2 or 3-digit Mobile Network Code in string format * @param alphal long alpha Operator Name String or Enhanced Operator Name String * @param alphas short alpha Operator Name String or Enhanced Operator Name String + * @param additionalPlmns a list of additional PLMN IDs broadcast by the cell * * @hide */ public CellIdentityGsm(int lac, int cid, int arfcn, int bsic, String mccStr, - String mncStr, String alphal, String alphas) { + String mncStr, String alphal, String alphas, + List<String> additionalPlmns) { super(TAG, CellInfo.TYPE_GSM, mccStr, mncStr, alphal, alphas); mLac = inRangeOrUnavailable(lac, 0, MAX_LAC); mCid = inRangeOrUnavailable(cid, 0, MAX_CID); mArfcn = inRangeOrUnavailable(arfcn, 0, MAX_ARFCN); mBsic = inRangeOrUnavailable(bsic, 0, MAX_BSIC); + mAdditionalPlmns = additionalPlmns; } /** @hide */ public CellIdentityGsm(android.hardware.radio.V1_0.CellIdentityGsm cid) { this(cid.lac, cid.cid, cid.arfcn, cid.bsic == (byte) 0xFF ? CellInfo.UNAVAILABLE : cid.bsic, - cid.mcc, cid.mnc, "", ""); + cid.mcc, cid.mnc, "", "", Collections.emptyList()); } /** @hide */ public CellIdentityGsm(android.hardware.radio.V1_2.CellIdentityGsm cid) { this(cid.base.lac, cid.base.cid, cid.base.arfcn, cid.base.bsic == (byte) 0xFF ? CellInfo.UNAVAILABLE : cid.base.bsic, cid.base.mcc, - cid.base.mnc, cid.operatorNames.alphaLong, cid.operatorNames.alphaShort); + cid.base.mnc, cid.operatorNames.alphaLong, cid.operatorNames.alphaShort, + Collections.emptyList()); + } + + /** @hide */ + public CellIdentityGsm(android.hardware.radio.V1_5.CellIdentityGsm cid) { + this(cid.base.base.lac, cid.base.base.cid, cid.base.base.arfcn, + cid.base.base.bsic == (byte) 0xFF ? CellInfo.UNAVAILABLE + : cid.base.base.bsic, cid.base.base.mcc, + cid.base.base.mnc, cid.base.operatorNames.alphaLong, + cid.base.operatorNames.alphaShort, cid.additionalPlmns); } private CellIdentityGsm(CellIdentityGsm cid) { this(cid.mLac, cid.mCid, cid.mArfcn, cid.mBsic, cid.mMccStr, - cid.mMncStr, cid.mAlphaLong, cid.mAlphaShort); + cid.mMncStr, cid.mAlphaLong, cid.mAlphaShort, cid.mAdditionalPlmns); } CellIdentityGsm copy() { @@ -107,7 +126,7 @@ public final class CellIdentityGsm extends CellIdentity { @Override public @NonNull CellIdentityGsm sanitizeLocationInfo() { return new CellIdentityGsm(CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, - CellInfo.UNAVAILABLE, mMccStr, mMncStr, mAlphaLong, mAlphaShort); + CellInfo.UNAVAILABLE, mMccStr, mMncStr, mAlphaLong, mAlphaShort, mAdditionalPlmns); } /** @@ -193,6 +212,14 @@ public final class CellIdentityGsm extends CellIdentity { } /** + * @return a list of additional PLMN IDs supported by this cell. + */ + @NonNull + public List<String> getAdditionalPlmns() { + return mAdditionalPlmns; + } + + /** * @deprecated Primary Scrambling Code is not applicable to GSM. * @return {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} - undefined for GSM */ @@ -215,7 +242,7 @@ public final class CellIdentityGsm extends CellIdentity { @Override public int hashCode() { - return Objects.hash(mLac, mCid, super.hashCode()); + return Objects.hash(mLac, mCid, mAdditionalPlmns.hashCode(), super.hashCode()); } @Override @@ -235,6 +262,7 @@ public final class CellIdentityGsm extends CellIdentity { && mBsic == o.mBsic && TextUtils.equals(mMccStr, o.mMccStr) && TextUtils.equals(mMncStr, o.mMncStr) + && mAdditionalPlmns.equals(o.mAdditionalPlmns) && super.equals(other); } @@ -249,6 +277,7 @@ public final class CellIdentityGsm extends CellIdentity { .append(" mMnc=").append(mMncStr) .append(" mAlphaLong=").append(mAlphaLong) .append(" mAlphaShort=").append(mAlphaShort) + .append(" mAdditionalPlmns=").append(mAdditionalPlmns) .append("}").toString(); } @@ -261,6 +290,7 @@ public final class CellIdentityGsm extends CellIdentity { dest.writeInt(mCid); dest.writeInt(mArfcn); dest.writeInt(mBsic); + dest.writeList(mAdditionalPlmns); } /** Construct from Parcel, type has already been processed */ @@ -270,6 +300,7 @@ public final class CellIdentityGsm extends CellIdentity { mCid = in.readInt(); mArfcn = in.readInt(); mBsic = in.readInt(); + mAdditionalPlmns = in.readArrayList(null); if (DBG) log(toString()); } diff --git a/telephony/java/android/telephony/CellIdentityLte.java b/telephony/java/android/telephony/CellIdentityLte.java index bc4655069dba..cf8fe6a3c345 100644 --- a/telephony/java/android/telephony/CellIdentityLte.java +++ b/telephony/java/android/telephony/CellIdentityLte.java @@ -24,6 +24,8 @@ import android.os.Parcel; import android.telephony.gsm.GsmCellLocation; import android.text.TextUtils; +import java.util.Collections; +import java.util.List; import java.util.Objects; /** @@ -50,6 +52,11 @@ public final class CellIdentityLte extends CellIdentity { // cell bandwidth, in kHz private final int mBandwidth; + // a list of additional PLMN-IDs reported for this cell + private final List<String> mAdditionalPlmns; + + private ClosedSubscriberGroupInfo mCsgInfo; + /** * @hide */ @@ -61,6 +68,8 @@ public final class CellIdentityLte extends CellIdentity { mTac = CellInfo.UNAVAILABLE; mEarfcn = CellInfo.UNAVAILABLE; mBandwidth = CellInfo.UNAVAILABLE; + mAdditionalPlmns = Collections.emptyList(); + mCsgInfo = null; } /** @@ -76,7 +85,7 @@ public final class CellIdentityLte extends CellIdentity { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public CellIdentityLte(int mcc, int mnc, int ci, int pci, int tac) { this(ci, pci, tac, CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, String.valueOf(mcc), - String.valueOf(mnc), null, null); + String.valueOf(mnc), null, null, Collections.emptyList(), null); } /** @@ -90,34 +99,49 @@ public final class CellIdentityLte extends CellIdentity { * @param mncStr 2 or 3-digit Mobile Network Code in string format * @param alphal long alpha Operator Name String or Enhanced Operator Name String * @param alphas short alpha Operator Name String or Enhanced Operator Name String + * @param additionalPlmns a list of additional PLMN IDs broadcast by the cell + * @param csgInfo info about the closed subscriber group broadcast by the cell * * @hide */ public CellIdentityLte(int ci, int pci, int tac, int earfcn, int bandwidth, String mccStr, - String mncStr, String alphal, String alphas) { + String mncStr, String alphal, String alphas, List<String> additionalPlmns, + ClosedSubscriberGroupInfo csgInfo) { super(TAG, CellInfo.TYPE_LTE, mccStr, mncStr, alphal, alphas); mCi = inRangeOrUnavailable(ci, 0, MAX_CI); mPci = inRangeOrUnavailable(pci, 0, MAX_PCI); mTac = inRangeOrUnavailable(tac, 0, MAX_TAC); mEarfcn = inRangeOrUnavailable(earfcn, 0, MAX_EARFCN); mBandwidth = inRangeOrUnavailable(bandwidth, 0, MAX_BANDWIDTH); + mAdditionalPlmns = additionalPlmns; + mCsgInfo = csgInfo; } /** @hide */ public CellIdentityLte(android.hardware.radio.V1_0.CellIdentityLte cid) { - this(cid.ci, cid.pci, cid.tac, cid.earfcn, CellInfo.UNAVAILABLE, cid.mcc, cid.mnc, "", ""); + this(cid.ci, cid.pci, cid.tac, cid.earfcn, + CellInfo.UNAVAILABLE, cid.mcc, cid.mnc, "", "", Collections.emptyList(), null); } /** @hide */ public CellIdentityLte(android.hardware.radio.V1_2.CellIdentityLte cid) { this(cid.base.ci, cid.base.pci, cid.base.tac, cid.base.earfcn, cid.bandwidth, cid.base.mcc, cid.base.mnc, cid.operatorNames.alphaLong, - cid.operatorNames.alphaShort); + cid.operatorNames.alphaShort, Collections.emptyList(), null); + } + + /** @hide */ + public CellIdentityLte(android.hardware.radio.V1_5.CellIdentityLte cid) { + this(cid.base.base.ci, cid.base.base.pci, cid.base.base.tac, cid.base.base.earfcn, + cid.base.bandwidth, cid.base.base.mcc, cid.base.base.mnc, + cid.base.operatorNames.alphaLong, cid.base.operatorNames.alphaShort, + cid.additionalPlmns, cid.optionalCsgInfo.csgInfo() != null + ? new ClosedSubscriberGroupInfo(cid.optionalCsgInfo.csgInfo()) : null); } private CellIdentityLte(CellIdentityLte cid) { this(cid.mCi, cid.mPci, cid.mTac, cid.mEarfcn, cid.mBandwidth, cid.mMccStr, - cid.mMncStr, cid.mAlphaLong, cid.mAlphaShort); + cid.mMncStr, cid.mAlphaLong, cid.mAlphaShort, cid.mAdditionalPlmns, cid.mCsgInfo); } /** @hide */ @@ -125,7 +149,7 @@ public final class CellIdentityLte extends CellIdentity { public @NonNull CellIdentityLte sanitizeLocationInfo() { return new CellIdentityLte(CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, - mMccStr, mMncStr, mAlphaLong, mAlphaShort); + mMccStr, mMncStr, mAlphaLong, mAlphaShort, mAdditionalPlmns, null); } CellIdentityLte copy() { @@ -185,6 +209,19 @@ public final class CellIdentityLte extends CellIdentity { } /** + * Get bands of the cell + * + * Reference: 3GPP TS 36.101 section 5.5 + * + * @return List of band number or empty list if not available. + */ + @NonNull + public List<Integer> getBands() { + // Todo: Add actual support + return Collections.emptyList(); + } + + /** * @return Cell bandwidth in kHz, * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable. */ @@ -223,6 +260,22 @@ public final class CellIdentityLte extends CellIdentity { } /** + * @return a list of additional PLMN IDs supported by this cell. + */ + @NonNull + public List<String> getAdditionalPlmns() { + return mAdditionalPlmns; + } + + /** + * @return closed subscriber group information about the cell if available, otherwise null. + */ + @Nullable + public ClosedSubscriberGroupInfo getClosedSubscriberGroupInfo() { + return mCsgInfo; + } + + /** * A hack to allow tunneling of LTE information via GsmCellLocation * so that older Network Location Providers can return some information * on LTE only networks, see bug 9228974. @@ -247,7 +300,8 @@ public final class CellIdentityLte extends CellIdentity { @Override public int hashCode() { - return Objects.hash(mCi, mPci, mTac, super.hashCode()); + return Objects.hash(mCi, mPci, mTac, + mAdditionalPlmns.hashCode(), mCsgInfo, super.hashCode()); } @Override @@ -268,6 +322,8 @@ public final class CellIdentityLte extends CellIdentity { && mBandwidth == o.mBandwidth && TextUtils.equals(mMccStr, o.mMccStr) && TextUtils.equals(mMncStr, o.mMncStr) + && mAdditionalPlmns.equals(o.mAdditionalPlmns) + && Objects.equals(mCsgInfo, o.mCsgInfo) && super.equals(other); } @@ -283,6 +339,8 @@ public final class CellIdentityLte extends CellIdentity { .append(" mMnc=").append(mMncStr) .append(" mAlphaLong=").append(mAlphaLong) .append(" mAlphaShort=").append(mAlphaShort) + .append(" mAdditionalPlmns=").append(mAdditionalPlmns) + .append(" mCsgInfo=").append(mCsgInfo) .append("}").toString(); } @@ -296,6 +354,8 @@ public final class CellIdentityLte extends CellIdentity { dest.writeInt(mTac); dest.writeInt(mEarfcn); dest.writeInt(mBandwidth); + dest.writeList(mAdditionalPlmns); + dest.writeParcelable(mCsgInfo, flags); } /** Construct from Parcel, type has already been processed */ @@ -306,7 +366,8 @@ public final class CellIdentityLte extends CellIdentity { mTac = in.readInt(); mEarfcn = in.readInt(); mBandwidth = in.readInt(); - + mAdditionalPlmns = in.readArrayList(null); + mCsgInfo = in.readParcelable(null); if (DBG) log(toString()); } diff --git a/telephony/java/android/telephony/CellIdentityNr.java b/telephony/java/android/telephony/CellIdentityNr.java index f08a5807c2b6..d4f181fc735a 100644 --- a/telephony/java/android/telephony/CellIdentityNr.java +++ b/telephony/java/android/telephony/CellIdentityNr.java @@ -20,8 +20,12 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Parcel; +import android.telephony.AccessNetworkConstants.NgranBands.NgranBand; import android.telephony.gsm.GsmCellLocation; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Objects; /** @@ -39,40 +43,58 @@ public final class CellIdentityNr extends CellIdentity { private final int mPci; private final int mTac; private final long mNci; + private final List<Integer> mBands; + + // a list of additional PLMN-IDs reported for this cell + private final List<String> mAdditionalPlmns; /** * * @param pci Physical Cell Id in range [0, 1007]. * @param tac 16-bit Tracking Area Code. * @param nrArfcn NR Absolute Radio Frequency Channel Number, in range [0, 3279165]. + * @param bands Bands used by the cell. Band number defined in 3GPP TS 38.101-1 and TS 38.101-2. * @param mccStr 3-digit Mobile Country Code in string format. * @param mncStr 2 or 3-digit Mobile Network Code in string format. * @param nci The 36-bit NR Cell Identity in range [0, 68719476735]. * @param alphal long alpha Operator Name String or Enhanced Operator Name String. * @param alphas short alpha Operator Name String or Enhanced Operator Name String. + * @param additionalPlmns a list of additional PLMN IDs broadcast by the cell * * @hide */ - public CellIdentityNr(int pci, int tac, int nrArfcn, String mccStr, String mncStr, - long nci, String alphal, String alphas) { + public CellIdentityNr(int pci, int tac, int nrArfcn, @NgranBand List<Integer> bands, + String mccStr, String mncStr, long nci, String alphal, String alphas, + List<String> additionalPlmns) { super(TAG, CellInfo.TYPE_NR, mccStr, mncStr, alphal, alphas); mPci = inRangeOrUnavailable(pci, 0, MAX_PCI); mTac = inRangeOrUnavailable(tac, 0, MAX_TAC); mNrArfcn = inRangeOrUnavailable(nrArfcn, 0, MAX_NRARFCN); + mBands = new ArrayList<>(bands); mNci = inRangeOrUnavailable(nci, 0, MAX_NCI); + mAdditionalPlmns = new ArrayList<>(additionalPlmns); } /** @hide */ public CellIdentityNr(android.hardware.radio.V1_4.CellIdentityNr cid) { - this(cid.pci, cid.tac, cid.nrarfcn, cid.mcc, cid.mnc, cid.nci, cid.operatorNames.alphaLong, - cid.operatorNames.alphaShort); + this(cid.pci, cid.tac, cid.nrarfcn, Collections.emptyList(), cid.mcc, cid.mnc, cid.nci, + cid.operatorNames.alphaLong, cid.operatorNames.alphaShort, + Collections.emptyList()); + } + + /** @hide */ + public CellIdentityNr(android.hardware.radio.V1_5.CellIdentityNr cid) { + this(cid.base.pci, cid.base.tac, cid.base.nrarfcn, cid.bands, cid.base.mcc, cid.base.mnc, + cid.base.nci, cid.base.operatorNames.alphaLong, + cid.base.operatorNames.alphaShort, cid.additionalPlmns); } /** @hide */ @Override public @NonNull CellIdentityNr sanitizeLocationInfo() { - return new CellIdentityNr(CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, - mMccStr, mMncStr, CellInfo.UNAVAILABLE, mAlphaLong, mAlphaShort); + return new CellIdentityNr(CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, mNrArfcn, + mBands, mMccStr, mMncStr, CellInfo.UNAVAILABLE, mAlphaLong, mAlphaShort, + mAdditionalPlmns); } /** @@ -87,7 +109,8 @@ public final class CellIdentityNr extends CellIdentity { @Override public int hashCode() { - return Objects.hash(super.hashCode(), mPci, mTac, mNrArfcn, mNci); + return Objects.hash(super.hashCode(), mPci, mTac, + mNrArfcn, mBands.hashCode(), mNci, mAdditionalPlmns.hashCode()); } @Override @@ -98,7 +121,8 @@ public final class CellIdentityNr extends CellIdentity { CellIdentityNr o = (CellIdentityNr) other; return super.equals(o) && mPci == o.mPci && mTac == o.mTac && mNrArfcn == o.mNrArfcn - && mNci == o.mNci; + && mBands.equals(o.mBands) && mNci == o.mNci + && mAdditionalPlmns.equals(o.mAdditionalPlmns); } /** @@ -125,6 +149,20 @@ public final class CellIdentityNr extends CellIdentity { } /** + * Get bands of the cell + * + * Reference: TS 38.101-1 table 5.2-1 + * Reference: TS 38.101-2 table 5.2-1 + * + * @return List of band number or empty list if not available. + */ + @NgranBand + @NonNull + public List<Integer> getBands() { + return Collections.unmodifiableList(mBands); + } + + /** * Get the physical cell id. * @return Integer value in range [0, 1007] or {@link CellInfo#UNAVAILABLE} if unknown. */ @@ -158,17 +196,33 @@ public final class CellIdentityNr extends CellIdentity { return mMncStr; } + /** @hide */ + @Override + public int getChannelNumber() { + return mNrArfcn; + } + + /** + * @return a list of additional PLMN IDs supported by this cell. + */ + @NonNull + public List<String> getAdditionalPlmns() { + return Collections.unmodifiableList(mAdditionalPlmns); + } + @Override public String toString() { return new StringBuilder(TAG + ":{") .append(" mPci = ").append(mPci) .append(" mTac = ").append(mTac) .append(" mNrArfcn = ").append(mNrArfcn) + .append(" mBands = ").append(mBands) .append(" mMcc = ").append(mMccStr) .append(" mMnc = ").append(mMncStr) .append(" mNci = ").append(mNci) .append(" mAlphaLong = ").append(mAlphaLong) .append(" mAlphaShort = ").append(mAlphaShort) + .append(" mAdditionalPlmns = ").append(mAdditionalPlmns) .append(" }") .toString(); } @@ -179,7 +233,9 @@ public final class CellIdentityNr extends CellIdentity { dest.writeInt(mPci); dest.writeInt(mTac); dest.writeInt(mNrArfcn); + dest.writeList(mBands); dest.writeLong(mNci); + dest.writeList(mAdditionalPlmns); } /** Construct from Parcel, type has already been processed */ @@ -188,7 +244,9 @@ public final class CellIdentityNr extends CellIdentity { mPci = in.readInt(); mTac = in.readInt(); mNrArfcn = in.readInt(); + mBands = in.readArrayList(null); mNci = in.readLong(); + mAdditionalPlmns = in.readArrayList(null); } /** Implement the Parcelable interface */ diff --git a/telephony/java/android/telephony/CellIdentityTdscdma.java b/telephony/java/android/telephony/CellIdentityTdscdma.java index 4bb3a95a9f1d..2ff351c17e9a 100644 --- a/telephony/java/android/telephony/CellIdentityTdscdma.java +++ b/telephony/java/android/telephony/CellIdentityTdscdma.java @@ -21,6 +21,8 @@ import android.annotation.Nullable; import android.os.Parcel; import android.telephony.gsm.GsmCellLocation; +import java.util.Collections; +import java.util.List; import java.util.Objects; /** @@ -46,6 +48,11 @@ public final class CellIdentityTdscdma extends CellIdentity { // 16-bit UMTS Absolute RF Channel Number described in TS 25.101 sec. 5.4.3 private final int mUarfcn; + // a list of additional PLMN-IDs reported for this cell + private final List<String> mAdditionalPlmns; + + private ClosedSubscriberGroupInfo mCsgInfo; + /** * @hide */ @@ -55,6 +62,8 @@ public final class CellIdentityTdscdma extends CellIdentity { mCid = CellInfo.UNAVAILABLE; mCpid = CellInfo.UNAVAILABLE; mUarfcn = CellInfo.UNAVAILABLE; + mAdditionalPlmns = Collections.emptyList(); + mCsgInfo = null; } /** @@ -68,39 +77,57 @@ public final class CellIdentityTdscdma extends CellIdentity { * @param uarfcn 16-bit UMTS Absolute RF Channel Number described in TS 25.101 sec. 5.4.3 * @param alphal long alpha Operator Name String or Enhanced Operator Name String * @param alphas short alpha Operator Name String or Enhanced Operator Name String + * @param additionalPlmns a list of additional PLMN IDs broadcast by the cell + * @param csgInfo info about the closed subscriber group broadcast by the cell * * @hide */ public CellIdentityTdscdma(String mcc, String mnc, int lac, int cid, int cpid, int uarfcn, - String alphal, String alphas) { + String alphal, String alphas, @NonNull List<String> additionalPlmns, + ClosedSubscriberGroupInfo csgInfo) { super(TAG, CellInfo.TYPE_TDSCDMA, mcc, mnc, alphal, alphas); mLac = inRangeOrUnavailable(lac, 0, MAX_LAC); mCid = inRangeOrUnavailable(cid, 0, MAX_CID); mCpid = inRangeOrUnavailable(cpid, 0, MAX_CPID); mUarfcn = inRangeOrUnavailable(uarfcn, 0, MAX_UARFCN); + mAdditionalPlmns = additionalPlmns; + mCsgInfo = csgInfo; } private CellIdentityTdscdma(CellIdentityTdscdma cid) { this(cid.mMccStr, cid.mMncStr, cid.mLac, cid.mCid, - cid.mCpid, cid.mUarfcn, cid.mAlphaLong, cid.mAlphaShort); + cid.mCpid, cid.mUarfcn, cid.mAlphaLong, + cid.mAlphaShort, cid.mAdditionalPlmns, cid.mCsgInfo); } /** @hide */ public CellIdentityTdscdma(android.hardware.radio.V1_0.CellIdentityTdscdma cid) { - this(cid.mcc, cid.mnc, cid.lac, cid.cid, cid.cpid, CellInfo.UNAVAILABLE, "", ""); + this(cid.mcc, cid.mnc, cid.lac, cid.cid, cid.cpid, CellInfo.UNAVAILABLE, "", "", + Collections.emptyList(), null); } /** @hide */ public CellIdentityTdscdma(android.hardware.radio.V1_2.CellIdentityTdscdma cid) { this(cid.base.mcc, cid.base.mnc, cid.base.lac, cid.base.cid, cid.base.cpid, - cid.uarfcn, cid.operatorNames.alphaLong, cid.operatorNames.alphaShort); + cid.uarfcn, cid.operatorNames.alphaLong, cid.operatorNames.alphaShort, + Collections.emptyList(), null); + } + + /** @hide */ + public CellIdentityTdscdma(android.hardware.radio.V1_5.CellIdentityTdscdma cid) { + this(cid.base.base.mcc, cid.base.base.mnc, cid.base.base.lac, cid.base.base.cid, + cid.base.base.cpid, cid.base.uarfcn, cid.base.operatorNames.alphaLong, + cid.base.operatorNames.alphaShort, + cid.additionalPlmns, cid.optionalCsgInfo.csgInfo() != null + ? new ClosedSubscriberGroupInfo(cid.optionalCsgInfo.csgInfo()) : null); } /** @hide */ @Override public @NonNull CellIdentityTdscdma sanitizeLocationInfo() { return new CellIdentityTdscdma(mMccStr, mMncStr, CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, - CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, mAlphaLong, mAlphaShort); + CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, mAlphaLong, mAlphaShort, + mAdditionalPlmns, null); } CellIdentityTdscdma copy() { @@ -171,6 +198,22 @@ public final class CellIdentityTdscdma extends CellIdentity { return mUarfcn; } + /** + * @return a list of additional PLMN IDs supported by this cell. + */ + @NonNull + public List<String> getAdditionalPlmns() { + return mAdditionalPlmns; + } + + /** + * @return closed subscriber group information about the cell if available, otherwise null. + */ + @Nullable + public ClosedSubscriberGroupInfo getClosedSubscriberGroupInfo() { + return mCsgInfo; + } + /** @hide */ @NonNull @Override @@ -198,12 +241,15 @@ public final class CellIdentityTdscdma extends CellIdentity { && mCid == o.mCid && mCpid == o.mCpid && mUarfcn == o.mUarfcn + && mAdditionalPlmns.equals(o.mAdditionalPlmns) + && Objects.equals(mCsgInfo, o.mCsgInfo) && super.equals(other); } @Override public int hashCode() { - return Objects.hash(mLac, mCid, mCpid, mUarfcn, super.hashCode()); + return Objects.hash(mLac, mCid, mCpid, mUarfcn, + mAdditionalPlmns.hashCode(), mCsgInfo, super.hashCode()); } @Override @@ -217,6 +263,8 @@ public final class CellIdentityTdscdma extends CellIdentity { .append(" mCid=").append(mCid) .append(" mCpid=").append(mCpid) .append(" mUarfcn=").append(mUarfcn) + .append(" mAdditionalPlmns=").append(mAdditionalPlmns) + .append(" mCsgInfo=").append(mCsgInfo) .append("}").toString(); } @@ -235,6 +283,8 @@ public final class CellIdentityTdscdma extends CellIdentity { dest.writeInt(mCid); dest.writeInt(mCpid); dest.writeInt(mUarfcn); + dest.writeList(mAdditionalPlmns); + dest.writeParcelable(mCsgInfo, flags); } /** Construct from Parcel, type has already been processed */ @@ -244,6 +294,8 @@ public final class CellIdentityTdscdma extends CellIdentity { mCid = in.readInt(); mCpid = in.readInt(); mUarfcn = in.readInt(); + mAdditionalPlmns = in.readArrayList(null); + mCsgInfo = in.readParcelable(null); if (DBG) log(toString()); } diff --git a/telephony/java/android/telephony/CellIdentityWcdma.java b/telephony/java/android/telephony/CellIdentityWcdma.java index 4ecd134c4ba9..9be42a17677b 100644 --- a/telephony/java/android/telephony/CellIdentityWcdma.java +++ b/telephony/java/android/telephony/CellIdentityWcdma.java @@ -23,6 +23,8 @@ import android.os.Parcel; import android.telephony.gsm.GsmCellLocation; import android.text.TextUtils; +import java.util.Collections; +import java.util.List; import java.util.Objects; /** @@ -47,6 +49,12 @@ public final class CellIdentityWcdma extends CellIdentity { @UnsupportedAppUsage private final int mUarfcn; + // a list of additional PLMN-IDs reported for this cell + private final List<String> mAdditionalPlmns; + + @Nullable + private final ClosedSubscriberGroupInfo mCsgInfo; + /** * @hide */ @@ -56,6 +64,8 @@ public final class CellIdentityWcdma extends CellIdentity { mCid = CellInfo.UNAVAILABLE; mPsc = CellInfo.UNAVAILABLE; mUarfcn = CellInfo.UNAVAILABLE; + mAdditionalPlmns = Collections.emptyList(); + mCsgInfo = null; } /** @@ -68,33 +78,49 @@ public final class CellIdentityWcdma extends CellIdentity { * @param mncStr 2 or 3-digit Mobile Network Code in string format * @param alphal long alpha Operator Name String or Enhanced Operator Name String * @param alphas short alpha Operator Name String or Enhanced Operator Name String + * @param additionalPlmns a list of additional PLMN IDs broadcast by the cell + * @param csgInfo info about the closed subscriber group broadcast by the cell * * @hide */ public CellIdentityWcdma (int lac, int cid, int psc, int uarfcn, - String mccStr, String mncStr, String alphal, String alphas) { + String mccStr, String mncStr, String alphal, String alphas, + @NonNull List<String> additionalPlmns, + @Nullable ClosedSubscriberGroupInfo csgInfo) { super(TAG, CellInfo.TYPE_WCDMA, mccStr, mncStr, alphal, alphas); mLac = inRangeOrUnavailable(lac, 0, MAX_LAC); mCid = inRangeOrUnavailable(cid, 0, MAX_CID); mPsc = inRangeOrUnavailable(psc, 0, MAX_PSC); mUarfcn = inRangeOrUnavailable(uarfcn, 0, MAX_UARFCN); + mAdditionalPlmns = additionalPlmns; + mCsgInfo = csgInfo; } /** @hide */ public CellIdentityWcdma(android.hardware.radio.V1_0.CellIdentityWcdma cid) { - this(cid.lac, cid.cid, cid.psc, cid.uarfcn, cid.mcc, cid.mnc, "", ""); + this(cid.lac, cid.cid, cid.psc, cid.uarfcn, cid.mcc, cid.mnc, "", "", + Collections.emptyList(), null); } /** @hide */ public CellIdentityWcdma(android.hardware.radio.V1_2.CellIdentityWcdma cid) { this(cid.base.lac, cid.base.cid, cid.base.psc, cid.base.uarfcn, cid.base.mcc, cid.base.mnc, cid.operatorNames.alphaLong, - cid.operatorNames.alphaShort); + cid.operatorNames.alphaShort, Collections.emptyList(), null); + } + + /** @hide */ + public CellIdentityWcdma(android.hardware.radio.V1_5.CellIdentityWcdma cid) { + this(cid.base.base.lac, cid.base.base.cid, cid.base.base.psc, cid.base.base.uarfcn, + cid.base.base.mcc, cid.base.base.mnc, cid.base.operatorNames.alphaLong, + cid.base.operatorNames.alphaShort, cid.additionalPlmns, + cid.optionalCsgInfo.csgInfo() != null + ? new ClosedSubscriberGroupInfo(cid.optionalCsgInfo.csgInfo()) : null); } private CellIdentityWcdma(CellIdentityWcdma cid) { this(cid.mLac, cid.mCid, cid.mPsc, cid.mUarfcn, cid.mMccStr, - cid.mMncStr, cid.mAlphaLong, cid.mAlphaShort); + cid.mMncStr, cid.mAlphaLong, cid.mAlphaShort, cid.mAdditionalPlmns, cid.mCsgInfo); } /** @hide */ @@ -102,7 +128,7 @@ public final class CellIdentityWcdma extends CellIdentity { public @NonNull CellIdentityWcdma sanitizeLocationInfo() { return new CellIdentityWcdma(CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, mMccStr, mMncStr, - mAlphaLong, mAlphaShort); + mAlphaLong, mAlphaShort, mAdditionalPlmns, null); } CellIdentityWcdma copy() { @@ -180,7 +206,7 @@ public final class CellIdentityWcdma extends CellIdentity { @Override public int hashCode() { - return Objects.hash(mLac, mCid, mPsc, super.hashCode()); + return Objects.hash(mLac, mCid, mPsc, mAdditionalPlmns.hashCode(), super.hashCode()); } /** @@ -197,6 +223,22 @@ public final class CellIdentityWcdma extends CellIdentity { return mUarfcn; } + /** + * @return a list of additional PLMN IDs supported by this cell. + */ + @NonNull + public List<String> getAdditionalPlmns() { + return mAdditionalPlmns; + } + + /** + * @return closed subscriber group information about the cell if available, otherwise null. + */ + @Nullable + public ClosedSubscriberGroupInfo getClosedSubscriberGroupInfo() { + return mCsgInfo; + } + /** @hide */ @NonNull @Override @@ -228,6 +270,8 @@ public final class CellIdentityWcdma extends CellIdentity { && mUarfcn == o.mUarfcn && TextUtils.equals(mMccStr, o.mMccStr) && TextUtils.equals(mMncStr, o.mMncStr) + && mAdditionalPlmns.equals(o.mAdditionalPlmns) + && Objects.equals(mCsgInfo, o.mCsgInfo) && super.equals(other); } @@ -242,6 +286,8 @@ public final class CellIdentityWcdma extends CellIdentity { .append(" mMnc=").append(mMncStr) .append(" mAlphaLong=").append(mAlphaLong) .append(" mAlphaShort=").append(mAlphaShort) + .append(" mAdditionalPlmns=").append(mAdditionalPlmns) + .append(" mCsgInfo=").append(mCsgInfo) .append("}").toString(); } @@ -254,6 +300,8 @@ public final class CellIdentityWcdma extends CellIdentity { dest.writeInt(mCid); dest.writeInt(mPsc); dest.writeInt(mUarfcn); + dest.writeList(mAdditionalPlmns); + dest.writeParcelable(mCsgInfo, flags); } /** Construct from Parcel, type has already been processed */ @@ -263,6 +311,8 @@ public final class CellIdentityWcdma extends CellIdentity { mCid = in.readInt(); mPsc = in.readInt(); mUarfcn = in.readInt(); + mAdditionalPlmns = in.readArrayList(null); + mCsgInfo = in.readParcelable(null); if (DBG) log(toString()); } diff --git a/telephony/java/android/telephony/ClosedSubscriberGroupInfo.aidl b/telephony/java/android/telephony/ClosedSubscriberGroupInfo.aidl new file mode 100644 index 000000000000..cbe76381a608 --- /dev/null +++ b/telephony/java/android/telephony/ClosedSubscriberGroupInfo.aidl @@ -0,0 +1,20 @@ +/* +** +** Copyright 2020, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package android.telephony; + +parcelable ClosedSubscriberGroupInfo; diff --git a/telephony/java/android/telephony/ClosedSubscriberGroupInfo.java b/telephony/java/android/telephony/ClosedSubscriberGroupInfo.java new file mode 100644 index 000000000000..e7dfe634590d --- /dev/null +++ b/telephony/java/android/telephony/ClosedSubscriberGroupInfo.java @@ -0,0 +1,156 @@ +/* + * 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.telephony; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Information to represent a closed subscriber group. + */ +public final class ClosedSubscriberGroupInfo implements Parcelable { + private static final String TAG = "ClosedSubscriberGroupInfo"; + + private final boolean mCsgIndicator; + + private final String mHomeNodebName; + + private final int mCsgIdentity; + + /** @hide */ + public ClosedSubscriberGroupInfo(boolean csgIndicator, @Nullable String homeNodebName, + int csgIdentity) { + mCsgIndicator = csgIndicator; + mHomeNodebName = (homeNodebName == null) ? "" : homeNodebName; + mCsgIdentity = csgIdentity; + } + + /** @hide */ + public ClosedSubscriberGroupInfo( + @NonNull android.hardware.radio.V1_5.ClosedSubscriberGroupInfo csgInfo) { + this(csgInfo.csgIndication, csgInfo.homeNodebName, csgInfo.csgIdentity); + } + + /** + * Indicates whether the cell is restricted to only CSG members. + * + * A cell not broadcasting the CSG Indication but reporting CSG information is considered a + * Hybrid Cell. + * Refer to the "csg-Indication" field in 3GPP TS 36.331 section 6.2.2 + * SystemInformationBlockType1. + * Also refer to "CSG Indicator" in 3GPP TS 25.331 section 10.2.48.8.1 and TS 25.304. + * + * @return true if the cell is restricted to group members only. + */ + public boolean getCsgIndicator() { + return mCsgIndicator; + } + + /** + * Returns human-readable name of the closed subscriber group operating this cell (Node-B). + * + * Refer to "hnb-Name" in TS 36.331 section 6.2.2 SystemInformationBlockType9. + * Also refer to "HNB Name" in 3GPP TS25.331 section 10.2.48.8.23 and TS 23.003 section 4.8. + * + * @return the home Node-B name if available. + */ + public @NonNull String getHomeNodebName() { + return mHomeNodebName; + } + + /** + * The identity of the closed subscriber group that the cell belongs to. + * + * Refer to "CSG-Identity" in TS 36.336 section 6.3.4. + * Also refer to "CSG Identity" in 3GPP TS 25.331 section 10.3.2.8 and TS 23.003 section 4.7. + * + * @return the unique 27-bit CSG Identity. + */ + @IntRange(from = 0, to = 0x7FFFFFF) + public int getCsgIdentity() { + return mCsgIdentity; + } + + @Override + public int hashCode() { + return Objects.hash(mCsgIndicator, mHomeNodebName, mCsgIdentity); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof ClosedSubscriberGroupInfo)) { + return false; + } + + ClosedSubscriberGroupInfo o = (ClosedSubscriberGroupInfo) other; + return mCsgIndicator == o.mCsgIndicator && mHomeNodebName == o.mHomeNodebName + && mCsgIdentity == o.mCsgIdentity; + } + + @Override + public String toString() { + return new StringBuilder(TAG + ":{") + .append(" mCsgIndicator = ").append(mCsgIndicator) + .append(" mHomeNodebName = ").append(mHomeNodebName) + .append(" mCsgIdentity = ").append(mCsgIdentity) + .toString(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int type) { + dest.writeBoolean(mCsgIndicator); + dest.writeString(mHomeNodebName); + dest.writeInt(mCsgIdentity); + } + + /** Construct from Parcel, type has already been processed */ + private ClosedSubscriberGroupInfo(Parcel in) { + this(in.readBoolean(), in.readString(), in.readInt()); + } + + /** + * Implement the Parcelable interface + */ + @Override + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface */ + public static final @android.annotation.NonNull Creator<ClosedSubscriberGroupInfo> CREATOR = + new Creator<ClosedSubscriberGroupInfo>() { + @Override + public ClosedSubscriberGroupInfo createFromParcel(Parcel in) { + return createFromParcelBody(in); + } + + @Override + public ClosedSubscriberGroupInfo[] newArray(int size) { + return new ClosedSubscriberGroupInfo[size]; + } + }; + + /** @hide */ + protected static ClosedSubscriberGroupInfo createFromParcelBody(Parcel in) { + return new ClosedSubscriberGroupInfo(in); + } +} diff --git a/telephony/java/android/telephony/ImsManager.java b/telephony/java/android/telephony/ImsManager.java index c706d288b7f2..9b4292f42172 100644 --- a/telephony/java/android/telephony/ImsManager.java +++ b/telephony/java/android/telephony/ImsManager.java @@ -54,6 +54,43 @@ public class ImsManager { "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION"; /** + * An intent action indicating that IMS registration for WiFi calling has resulted in an error. + * Contains error information that should be displayed to the user. + * <p> + * This intent will contain the following extra key/value pairs: + * {@link #EXTRA_WFC_REGISTRATION_FAILURE_TITLE} + * and {@link #EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE}, which contain carrier specific + * error information that should be displayed to the user. + * <p> + * Usage: This intent is sent as an ordered broadcast. If the settings application is going + * to show the error information specified to the user, it should respond to + * {@link android.content.BroadcastReceiver#setResultCode(int)} with + * {@link android.app.Activity#RESULT_CANCELED}, which will signal to the framework that the + * event was handled. If the framework does not receive a response to the ordered broadcast, + * it will then show a notification to the user indicating that there was a registration + * failure. + */ + @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_WFC_IMS_REGISTRATION_ERROR = + "android.telephony.ims.action.WFC_IMS_REGISTRATION_ERROR"; + + /** + * An extra key corresponding to a String value which contains the carrier specific title to be + * displayed as part of the message shown to the user when there is an error registering for + * WiFi calling. + */ + public static final String EXTRA_WFC_REGISTRATION_FAILURE_TITLE = + "android.telephony.ims.extra.WFC_REGISTRATION_FAILURE_TITLE"; + + /** + * An extra key corresponding to a String value which contains the carrier specific message to + * be displayed as part of the message shown to the user when there is an error registering for + * WiFi calling. + */ + public static final String EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE = + "android.telephony.ims.extra.WFC_REGISTRATION_FAILURE_MESSAGE"; + + /** * Use {@link Context#getSystemService(String)} to get an instance of this class. * @hide */ diff --git a/telephony/java/android/telephony/ModemInfo.java b/telephony/java/android/telephony/ModemInfo.java deleted file mode 100644 index c0833af954d8..000000000000 --- a/telephony/java/android/telephony/ModemInfo.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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.telephony; - -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.Objects; - -/** - * Information of a single logical modem indicating - * its id, supported rats and whether it supports voice or data, etc. - * @hide - */ -public class ModemInfo implements Parcelable { - public final int modemId; - public final int rat; /* bitset */ - public final boolean isVoiceSupported; - public final boolean isDataSupported; - - // TODO b/121394331: Clean up this class after V1_1.PhoneCapability cleanup. - public ModemInfo(int modemId) { - this(modemId, 0, true, true); - } - - public ModemInfo(int modemId, int rat, boolean isVoiceSupported, boolean isDataSupported) { - this.modemId = modemId; - this.rat = rat; - this.isVoiceSupported = isVoiceSupported; - this.isDataSupported = isDataSupported; - } - - public ModemInfo(Parcel in) { - modemId = in.readInt(); - rat = in.readInt(); - isVoiceSupported = in.readBoolean(); - isDataSupported = in.readBoolean(); - } - - @Override - public String toString() { - return "modemId=" + modemId + " rat=" + rat + " isVoiceSupported:" + isVoiceSupported - + " isDataSupported:" + isDataSupported; - } - - @Override - public int hashCode() { - return Objects.hash(modemId, rat, isVoiceSupported, isDataSupported); - } - - @Override - public boolean equals(Object o) { - if (o == null || !(o instanceof ModemInfo) || hashCode() != o.hashCode()) { - return false; - } - - if (this == o) { - return true; - } - - ModemInfo s = (ModemInfo) o; - - return (modemId == s.modemId - && rat == s.rat - && isVoiceSupported == s.isVoiceSupported - && isDataSupported == s.isDataSupported); - } - - /** - * {@link Parcelable#describeContents} - */ - public @ContentsFlags int describeContents() { - return 0; - } - - /** - * {@link Parcelable#writeToParcel} - */ - public void writeToParcel(Parcel dest, @WriteFlags int flags) { - dest.writeInt(modemId); - dest.writeInt(rat); - dest.writeBoolean(isVoiceSupported); - dest.writeBoolean(isDataSupported); - } - - public static final @android.annotation.NonNull Parcelable.Creator<ModemInfo> CREATOR = new Parcelable.Creator() { - public ModemInfo createFromParcel(Parcel in) { - return new ModemInfo(in); - } - - public ModemInfo[] newArray(int size) { - return new ModemInfo[size]; - } - }; -} diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java index 0cfb8c56320a..094b8b0d1631 100644 --- a/telephony/java/android/telephony/PreciseDataConnectionState.java +++ b/telephony/java/android/telephony/PreciseDataConnectionState.java @@ -135,7 +135,7 @@ public final class PreciseDataConnectionState implements Parcelable { } /** - * To check the SDK version for {@link PreciseDataConnectionState#getDataConnectionState}. + * To check the SDK version for {@code PreciseDataConnectionState#getDataConnectionState}. */ @ChangeId @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java index 2f95a501ce2f..5cd7cf8fae8a 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -1658,7 +1658,7 @@ public final class SmsManager { } /** - * Copy a raw SMS PDU to the ICC. + * Copies a raw SMS PDU to the ICC. * ICC (Integrated Circuit Card) is the card of the device. * For example, this can be the SIM or USIM for GSM. * @@ -1672,21 +1672,26 @@ public final class SmsManager { * operation is performed on the correct subscription. * </p> * - * @param smsc the SMSC for this message, or NULL for the default SMSC - * @param pdu the raw PDU to store - * @param status message status (STATUS_ON_ICC_READ, STATUS_ON_ICC_UNREAD, - * STATUS_ON_ICC_SENT, STATUS_ON_ICC_UNSENT) - * @return true for success + * @param smsc the SMSC for this messag or null for the default SMSC. + * @param pdu the raw PDU to store. + * @param status message status. One of these status: + * <code>STATUS_ON_ICC_READ</code> + * <code>STATUS_ON_ICC_UNREAD</code> + * <code>STATUS_ON_ICC_SENT</code> + * <code>STATUS_ON_ICC_UNSENT</code> + * @return true for success. Otherwise false. * - * @throws IllegalArgumentException if pdu is NULL - * {@hide} + * @throws IllegalArgumentException if pdu is null. + * @hide */ - @UnsupportedAppUsage - public boolean copyMessageToIcc(byte[] smsc, byte[] pdu,int status) { + @SystemApi + @RequiresPermission(Manifest.permission.ACCESS_MESSAGES_ON_ICC) + public boolean copyMessageToIcc( + @Nullable byte[] smsc, @NonNull byte[] pdu, @StatusOnIcc int status) { boolean success = false; - if (null == pdu) { - throw new IllegalArgumentException("pdu is NULL"); + if (pdu == null) { + throw new IllegalArgumentException("pdu is null"); } try { ISms iSms = getISmsService(); @@ -1703,7 +1708,7 @@ public final class SmsManager { } /** - * Delete the specified message from the ICC. + * Deletes the specified message from the ICC. * ICC (Integrated Circuit Card) is the card of the device. * For example, this can be the SIM or USIM for GSM. * @@ -1787,7 +1792,7 @@ public final class SmsManager { } /** - * Retrieves all messages currently stored on ICC. + * Retrieves all messages currently stored on the ICC. * ICC (Integrated Circuit Card) is the card of the device. * For example, this can be the SIM or USIM for GSM. * @@ -1953,8 +1958,7 @@ public final class SmsManager { } /** - * Create a list of <code>SmsMessage</code>s from a list of RawSmsData - * records returned by <code>getAllMessagesFromIcc()</code> + * Creates a list of <code>SmsMessage</code>s from a list of SmsRawData records. * * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier * applications or the Telephony framework and will never trigger an SMS disambiguation @@ -1966,8 +1970,7 @@ public final class SmsManager { * operation is performed on the correct subscription. * </p> * - * @param records SMS EF records, returned by - * <code>getAllMessagesFromIcc</code> + * @param records SMS EF records. * @return <code>ArrayList</code> of <code>SmsMessage</code> objects. */ private ArrayList<SmsMessage> createMessageListFromRawRecords(List<SmsRawData> records) { @@ -1978,7 +1981,7 @@ public final class SmsManager { SmsRawData data = records.get(i); // List contains all records, including "free" records (null) if (data != null) { - SmsMessage sms = SmsMessage.createFromEfRecord(i+1, data.getBytes(), + SmsMessage sms = SmsMessage.createFromEfRecord(i + 1, data.getBytes(), getSubscriptionId()); if (sms != null) { messages.add(sms); @@ -2122,6 +2125,17 @@ public final class SmsManager { return ret; } + /** @hide */ + @IntDef(prefix = { "STATUS_ON_ICC_" }, value = { + STATUS_ON_ICC_FREE, + STATUS_ON_ICC_READ, + STATUS_ON_ICC_UNREAD, + STATUS_ON_ICC_SENT, + STATUS_ON_ICC_UNSENT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface StatusOnIcc {} + // see SmsMessage.getStatusOnIcc /** Free space (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */ diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java index eefbd44ad6de..7a30f143a3a4 100644 --- a/telephony/java/android/telephony/SmsMessage.java +++ b/telephony/java/android/telephony/SmsMessage.java @@ -278,41 +278,24 @@ public class SmsMessage { } /** - * Create an SmsMessage from an SMS EF record. + * Creates an SmsMessage from an SMS EF record. * - * @param index Index of SMS record. This should be index in ArrayList - * returned by SmsManager.getAllMessagesFromSim + 1. + * @param index Index of SMS EF record. * @param data Record data. * @return An SmsMessage representing the record. * * @hide */ public static SmsMessage createFromEfRecord(int index, byte[] data) { - SmsMessageBase wrappedMessage; - - if (isCdmaVoice()) { - wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromEfRecord( - index, data); - } else { - wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromEfRecord( - index, data); - } - - if (wrappedMessage != null) { - return new SmsMessage(wrappedMessage); - } else { - Rlog.e(LOG_TAG, "createFromEfRecord(): wrappedMessage is null"); - return null; - } + return createFromEfRecord(index, data, SmsManager.getDefaultSmsSubscriptionId()); } /** - * Create an SmsMessage from an SMS EF record. + * Creates an SmsMessage from an SMS EF record. * - * @param index Index of SMS record. This should be index in ArrayList - * returned by SmsManager.getAllMessagesFromSim + 1. + * @param index Index of SMS EF record. * @param data Record data. - * @param subId Subscription Id of the SMS + * @param subId Subscription Id associated with the record. * @return An SmsMessage representing the record. * * @hide @@ -602,13 +585,15 @@ public class SmsMessage { */ /** - * Get an SMS-SUBMIT PDU for a destination address and a message. + * Gets an SMS-SUBMIT PDU for a destination address and a message. * This method will not attempt to use any GSM national language 7 bit encodings. * - * @param scAddress Service Centre address. Null means use default. - * @return a <code>SubmitPdu</code> containing the encoded SC - * address, if applicable, and the encoded message. - * Returns null on encode error. + * @param scAddress Service Centre address. Null means use default. + * @param destinationAddress the address of the destination for the message. + * @param message string representation of the message payload. + * @param statusReportRequested indicates whether a report is requested for this message. + * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the + * encoded message. Returns null on encode error. */ public static SubmitPdu getSubmitPdu(String scAddress, String destinationAddress, String message, boolean statusReportRequested) { @@ -621,17 +606,16 @@ public class SmsMessage { } /** - * Get an SMS-SUBMIT PDU for a destination address and a message. + * Gets an SMS-SUBMIT PDU for a destination address and a message. * This method will not attempt to use any GSM national language 7 bit encodings. * - * @param scAddress Service Centre address. Null means use default. + * @param scAddress Service Centre address. Null means use default. * @param destinationAddress the address of the destination for the message. - * @param message String representation of the message payload. - * @param statusReportRequested Indicates whether a report is requested for this message. - * @param subId Subscription of the message - * @return a <code>SubmitPdu</code> containing the encoded SC - * address, if applicable, and the encoded message. - * Returns null on encode error. + * @param message string representation of the message payload. + * @param statusReportRequested indicates whether a report is requested for this message. + * @param subId subscription of the message. + * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the + * encoded message. Returns null on encode error. * @hide */ public static SubmitPdu getSubmitPdu(String scAddress, @@ -649,17 +633,16 @@ public class SmsMessage { } /** - * Get an SMS-SUBMIT PDU for a data message to a destination address & port. + * Gets an SMS-SUBMIT PDU for a data message to a destination address & port. * This method will not attempt to use any GSM national language 7 bit encodings. * - * @param scAddress Service Centre address. null == use default - * @param destinationAddress the address of the destination for the message - * @param destinationPort the port to deliver the message to at the - * destination - * @param data the data for the message - * @return a <code>SubmitPdu</code> containing the encoded SC - * address, if applicable, and the encoded message. - * Returns null on encode error. + * @param scAddress Service Centre address. Null means use default. + * @param destinationAddress the address of the destination for the message. + * @param destinationPort the port to deliver the message to at the destination. + * @param data the data for the message. + * @param statusReportRequested indicates whether a report is requested for this message. + * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the + * encoded message. Returns null on encode error. */ public static SubmitPdu getSubmitPdu(String scAddress, String destinationAddress, short destinationPort, byte[] data, @@ -677,6 +660,55 @@ public class SmsMessage { return new SubmitPdu(spb); } + // TODO: SubmitPdu class is used for SMS-DELIVER also now. Refactor for SubmitPdu and new + // DeliverPdu accordingly. + + /** + * Gets an SMS PDU to store in the ICC. + * + * @param subId subscription of the message. + * @param status message status. One of these status: + * <code>SmsManager.STATUS_ON_ICC_READ</code> + * <code>SmsManager.STATUS_ON_ICC_UNREAD</code> + * <code>SmsManager.STATUS_ON_ICC_SENT</code> + * <code>SmsManager.STATUS_ON_ICC_UNSENT</code> + * @param scAddress Service Centre address. Null means use default. + * @param address destination or originating address. + * @param message string representation of the message payload. + * @param date the time stamp the message was received. + * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the + * encoded message. Returns null on encode error. + * @hide + */ + @SystemApi + @Nullable + public static SubmitPdu getSmsPdu(int subId, @SmsManager.StatusOnIcc int status, + @Nullable String scAddress, @NonNull String address, @NonNull String message, + long date) { + SubmitPduBase spb; + if (isCdmaVoice(subId)) { // 3GPP2 format + if (status == SmsManager.STATUS_ON_ICC_READ + || status == SmsManager.STATUS_ON_ICC_UNREAD) { // Deliver PDU + spb = com.android.internal.telephony.cdma.SmsMessage.getDeliverPdu(address, + message, date); + } else { // Submit PDU + spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress, + address, message, false /* statusReportRequested */, null /* smsHeader */); + } + } else { // 3GPP format + if (status == SmsManager.STATUS_ON_ICC_READ + || status == SmsManager.STATUS_ON_ICC_UNREAD) { // Deliver PDU + spb = com.android.internal.telephony.gsm.SmsMessage.getDeliverPdu(scAddress, + address, message, date); + } else { // Submit PDU + spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress, + address, message, false /* statusReportRequested */, null /* header */); + } + } + + return spb != null ? new SubmitPdu(spb) : null; + } + /** * Get an SMS-SUBMIT PDU's encoded message. * This is used by Bluetooth MAP profile to handle long non UTF-8 SMS messages. diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 8de5b8541f56..63a85fa2845c 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -16,8 +16,8 @@ package android.telephony; -import static android.net.NetworkPolicyManager.OVERRIDE_CONGESTED; -import static android.net.NetworkPolicyManager.OVERRIDE_UNMETERED; +import static android.net.NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_CONGESTED; +import static android.net.NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_UNMETERED; import android.Manifest; import android.annotation.CallbackExecutor; @@ -45,6 +45,7 @@ import android.content.res.Resources; import android.database.ContentObserver; import android.net.INetworkPolicyManager; import android.net.NetworkCapabilities; +import android.net.NetworkPolicyManager; import android.net.Uri; import android.os.Binder; import android.os.Build; @@ -957,6 +958,11 @@ public class SubscriptionManager { mContext = context; } + private NetworkPolicyManager getNetworkPolicyManager() { + return (NetworkPolicyManager) mContext + .getSystemService(Context.NETWORK_POLICY_SERVICE); + } + /** * @deprecated developers should always obtain references directly from * {@link Context#getSystemService(Class)}. @@ -967,7 +973,7 @@ public class SubscriptionManager { .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); } - private final INetworkPolicyManager getNetworkPolicy() { + private INetworkPolicyManager getINetworkPolicyManager() { if (mNetworkPolicy == null) { mNetworkPolicy = INetworkPolicyManager.Stub.asInterface( TelephonyFrameworkInitializer @@ -2587,14 +2593,10 @@ public class SubscriptionManager { * outlined above. */ public @NonNull List<SubscriptionPlan> getSubscriptionPlans(int subId) { - try { - SubscriptionPlan[] subscriptionPlans = - getNetworkPolicy().getSubscriptionPlans(subId, mContext.getOpPackageName()); - return subscriptionPlans == null - ? Collections.emptyList() : Arrays.asList(subscriptionPlans); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + SubscriptionPlan[] subscriptionPlans = + getNetworkPolicyManager().getSubscriptionPlans(subId, mContext.getOpPackageName()); + return subscriptionPlans == null + ? Collections.emptyList() : Arrays.asList(subscriptionPlans); } /** @@ -2620,18 +2622,14 @@ public class SubscriptionManager { * defined in {@link SubscriptionPlan}. */ public void setSubscriptionPlans(int subId, @NonNull List<SubscriptionPlan> plans) { - try { - getNetworkPolicy().setSubscriptionPlans(subId, - plans.toArray(new SubscriptionPlan[plans.size()]), mContext.getOpPackageName()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + getNetworkPolicyManager().setSubscriptionPlans(subId, + plans.toArray(new SubscriptionPlan[plans.size()]), mContext.getOpPackageName()); } /** @hide */ private String getSubscriptionPlansOwner(int subId) { try { - return getNetworkPolicy().getSubscriptionPlansOwner(subId); + return getINetworkPolicyManager().getSubscriptionPlansOwner(subId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2662,13 +2660,10 @@ public class SubscriptionManager { */ public void setSubscriptionOverrideUnmetered(int subId, boolean overrideUnmetered, @DurationMillisLong long timeoutMillis) { - try { - final int overrideValue = overrideUnmetered ? OVERRIDE_UNMETERED : 0; - getNetworkPolicy().setSubscriptionOverride(subId, OVERRIDE_UNMETERED, overrideValue, - timeoutMillis, mContext.getOpPackageName()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + + final int overrideValue = overrideUnmetered ? SUBSCRIPTION_OVERRIDE_UNMETERED : 0; + getNetworkPolicyManager().setSubscriptionOverride(subId, SUBSCRIPTION_OVERRIDE_UNMETERED, + overrideValue, timeoutMillis, mContext.getOpPackageName()); } /** @@ -2697,13 +2692,9 @@ public class SubscriptionManager { */ public void setSubscriptionOverrideCongested(int subId, boolean overrideCongested, @DurationMillisLong long timeoutMillis) { - try { - final int overrideValue = overrideCongested ? OVERRIDE_CONGESTED : 0; - getNetworkPolicy().setSubscriptionOverride(subId, OVERRIDE_CONGESTED, overrideValue, - timeoutMillis, mContext.getOpPackageName()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + final int overrideValue = overrideCongested ? SUBSCRIPTION_OVERRIDE_CONGESTED : 0; + getNetworkPolicyManager().setSubscriptionOverride(subId, SUBSCRIPTION_OVERRIDE_CONGESTED, + overrideValue, timeoutMillis, mContext.getOpPackageName()); } /** diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 075b56b72c19..903be6f68282 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -50,7 +50,6 @@ import android.net.ConnectivityManager; import android.net.NetworkStats; import android.net.Uri; import android.os.AsyncTask; -import android.os.BatteryStats; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -161,8 +160,8 @@ public class TelephonyManager { * into the ResultReceiver Bundle. * @hide */ - public static final String MODEM_ACTIVITY_RESULT_KEY = - BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY; + @SystemApi + public static final String MODEM_ACTIVITY_RESULT_KEY = "controller_activity"; /** * The process name of the Phone app as well as many other apps that use this process name, such @@ -1245,6 +1244,80 @@ public class TelephonyManager { public static final String EXTRA_SUBSCRIPTION_ID = "android.telephony.extra.SUBSCRIPTION_ID"; /** + * Broadcast Action: The Service Provider string(s) have been updated. Activities or + * services that use these strings should update their display. + * + * <p>The intent will have the following extra values: + * <dl> + * <dt>{@link #EXTRA_SHOW_PLMN}</dt> + * <dd>Boolean that indicates whether the PLMN should be shown.</dd> + * <dt>{@link #EXTRA_PLMN}</dt> + * <dd>The operator name of the registered network, as a string.</dd> + * <dt>{@link #EXTRA_SHOW_SPN}</dt> + * <dd>Boolean that indicates whether the SPN should be shown.</dd> + * <dt>{@link #EXTRA_SPN}</dt> + * <dd>The service provider name, as a string.</dd> + * <dt>{@link #EXTRA_DATA_SPN}</dt> + * <dd>The service provider name for data service, as a string.</dd> + * </dl> + * + * Note that {@link #EXTRA_SHOW_PLMN} may indicate that {@link #EXTRA_PLMN} should be displayed, + * even though the value for {@link #EXTRA_PLMN} is null. This can happen, for example, if the + * phone has not registered to a network yet. In this case the receiver may substitute an + * appropriate placeholder string (eg, "No service"). + * + * It is recommended to display {@link #EXTRA_PLMN} before / above {@link #EXTRA_SPN} if + * both are displayed. + * + * <p>Note: this is a protected intent that can only be sent by the system. + * @hide + */ + @SystemApi + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_SERVICE_PROVIDERS_UPDATED = + "android.telephony.action.SERVICE_PROVIDERS_UPDATED"; + + /** + * String intent extra to be used with {@link ACTION_SERVICE_PROVIDERS_UPDATED} to indicate + * whether the PLMN should be shown. + * @hide + */ + @SystemApi + public static final String EXTRA_SHOW_PLMN = "android.telephony.extra.SHOW_PLMN"; + + /** + * String intent extra to be used with {@link ACTION_SERVICE_PROVIDERS_UPDATED} to indicate + * the operator name of the registered network. + * @hide + */ + @SystemApi + public static final String EXTRA_PLMN = "android.telephony.extra.PLMN"; + + /** + * String intent extra to be used with {@link ACTION_SERVICE_PROVIDERS_UPDATED} to indicate + * whether the PLMN should be shown. + * @hide + */ + @SystemApi + public static final String EXTRA_SHOW_SPN = "android.telephony.extra.SHOW_SPN"; + + /** + * String intent extra to be used with {@link ACTION_SERVICE_PROVIDERS_UPDATED} to indicate + * the service provider name. + * @hide + */ + @SystemApi + public static final String EXTRA_SPN = "android.telephony.extra.SPN"; + + /** + * String intent extra to be used with {@link ACTION_SERVICE_PROVIDERS_UPDATED} to indicate + * the service provider name for data service. + * @hide + */ + @SystemApi + public static final String EXTRA_DATA_SPN = "android.telephony.extra.DATA_SPN"; + + /** * Broadcast intent action indicating that when data stall recovery is attempted by Telephony, * intended for report every data stall recovery step attempted. * @@ -2708,7 +2781,7 @@ public class TelephonyManager { @UnsupportedAppUsage public boolean isNetworkRoaming(int subId) { int phoneId = SubscriptionManager.getPhoneId(subId); - return getTelephonyProperty(subId, TelephonyProperties.operator_is_roaming(), false); + return getTelephonyProperty(phoneId, TelephonyProperties.operator_is_roaming(), false); } /** @@ -7939,6 +8012,36 @@ public class TelephonyManager { * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling * app has carrier privileges (see {@link #hasCarrierPrivileges}). * + * @param operatorNumeric the PLMN ID of the network to select. + * @param ran the initial suggested radio access network type. + * If registration fails, the RAN is not available after, the RAN is not within the + * network types specified by {@link #setPreferredNetworkTypeBitmask}, or the value is + * {@link AccessNetworkConstants.AccessNetworkType#UNKNOWN}, modem will select + * the next best RAN for network registration. + * @param persistSelection whether the selection will persist until reboot. + * If true, only allows attaching to the selected PLMN until reboot; otherwise, + * attach to the chosen PLMN and resume normal network selection next time. + * @return {@code true} on success; {@code false} on any failure. + * @hide + */ + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + @SystemApi + public boolean setNetworkSelectionModeManual(@NonNull String operatorNumeric, + @AccessNetworkConstants.RadioAccessNetworkType int ran, boolean persistSelection) { + return setNetworkSelectionModeManual(new OperatorInfo("" /* operatorAlphaLong */, + "" /* operatorAlphaShort */, operatorNumeric, ran), persistSelection); + } + + /** + * Ask the radio to connect to the input network and change selection mode to manual. + * + * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the + * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()} + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). + * * @param operatorInfo included the PLMN id, long name, short name of the operator to attach to. * @param persistSelection whether the selection will persist until reboot. If true, only allows * attaching to the selected PLMN until reboot; otherwise, attach to the chosen PLMN and resume @@ -7992,6 +8095,30 @@ public class TelephonyManager { } /** + * Get the PLMN chosen for Manual Network Selection if active. + * Return empty string if in automatic selection. + * + * <p>Requires Permission: {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE + * READ_PRECISE_PHONE_STATE} or that the calling app has carrier privileges + * (see {@link #hasCarrierPrivileges}) + * + * @return manually selected network info on success or empty string on failure + */ + @SuppressAutoDoc // No support carrier privileges (b/72967236). + @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) + public @NonNull String getManualNetworkSelectionPlmn() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null && isManualNetworkSelectionAllowed()) { + return telephony.getManualNetworkSelectionPlmn(getSubId()); + } + } catch (RemoteException ex) { + Rlog.e(TAG, "getManualNetworkSelectionPlmn RemoteException", ex); + } + return ""; + } + + /** * Query Telephony to see if there has recently been an emergency SMS sent to the network by the * user and we are still within the time interval after the emergency SMS was sent that we are * considered in Emergency SMS mode. @@ -10206,7 +10333,8 @@ public class TelephonyManager { * Requests the modem activity info. The recipient will place the result * in `result`. * @param result The object on which the recipient will send the resulting - * {@link android.telephony.ModemActivityInfo} object. + * {@link android.telephony.ModemActivityInfo} object with key of + * {@link #MODEM_ACTIVITY_RESULT_KEY}. * @hide */ @SystemApi @@ -11043,15 +11171,18 @@ public class TelephonyManager { /** * Checks if manual network selection is allowed. * + * <p>Requires Permission: {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE + * READ_PRECISE_PHONE_STATE} or that the calling app has carrier privileges + * (see {@link #hasCarrierPrivileges}) + * * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}. * * @return {@code true} if manual network selection is allowed, otherwise return {@code false}. - * - * @hide */ - @SystemApi - @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + @SuppressAutoDoc // No support carrier privileges (b/72967236). + @RequiresPermission(anyOf = {android.Manifest.permission.READ_PRECISE_PHONE_STATE, + android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE}) public boolean isManualNetworkSelectionAllowed() { try { ITelephony telephony = getITelephony(); @@ -12810,4 +12941,22 @@ public class TelephonyManager { } return 0; } + + /** + * Called when userActivity is signalled in the power manager. + * This should only be called from system Uid. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void notifyUserActivity() { + try { + ITelephony service = getITelephony(); + if (service != null) { + service.userActivity(); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java index d5a48df149f1..27a70228a433 100644 --- a/telephony/java/android/telephony/euicc/EuiccManager.java +++ b/telephony/java/android/telephony/euicc/EuiccManager.java @@ -246,13 +246,56 @@ public class EuiccManager { * Key for an extra set on {@link PendingIntent} result callbacks providing a detailed result * code. * - * <p>This code is an implementation detail of the embedded subscription manager and is only - * intended for logging or debugging purposes. + * <p>The value of this key is an integer and contains two portions. The first byte is + * OperationCode and the reaming three bytes is the ErrorCode. + * + * OperationCode is the first byte of the result code and is a categorization which defines what + * type of operation took place when an error occurred. e.g {@link #OPERATION_DOWNLOAD} means + * the error is related to download.Since the OperationCode only uses at most one byte, the + * maximum allowed quantity is 255(0xFF). + * + * ErrorCode is the remaing three bytes of the result code, and it denotes what happened. + * e.g a combination of {@link #OPERATION_DOWNLOAD} and {@link #ERROR_TIME_OUT} will suggest the + * download operation has timed out. The only exception here is + * {@link #OPERATION_SMDX_SUBJECT_REASON_CODE}, where instead of ErrorCode, SubjectCode[5.2.6.1 + * from GSMA (SGP.22 v2.2) and ReasonCode[5.2.6.2] from GSMA (SGP.22 v2.2) are encoded. @see + * {@link #EXTRA_EMBEDDED_SUBSCRIPTION_SMDX_SUBJECT_CODE} and + * {@link #EXTRA_EMBEDDED_SUBSCRIPTION_SMDX_REASON_CODE} */ public static final String EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_DETAILED_CODE"; /** + * Key for an extra set on {@link PendingIntent} result callbacks providing a + * OperationCode of {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE}. + */ + public static final String EXTRA_EMBEDDED_SUBSCRIPTION_OPERATION_CODE = + "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_OPERATION_CODE"; + + /** + * Key for an extra set on {@link PendingIntent} result callbacks providing a + * ErrorCode of {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE}. + */ + public static final String EXTRA_EMBEDDED_SUBSCRIPTION_ERROR_CODE = + "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_ERROR_CODE"; + + /** + * Key for an extra set on {@link PendingIntent} result callbacks providing a + * SubjectCode[5.2.6.1] from GSMA (SGP.22 v2.2) decoded from + * {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE}. + */ + public static final String EXTRA_EMBEDDED_SUBSCRIPTION_SMDX_SUBJECT_CODE = + "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_SMDX_SUBJECT_CODE"; + + /** + * Key for an extra set on {@link PendingIntent} result callbacks providing a + * ReasonCode[5.2.6.2] from GSMA (SGP.22 v2.2) decoded from + * {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE}. + */ + public static final String EXTRA_EMBEDDED_SUBSCRIPTION_SMDX_REASON_CODE = + "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_SMDX_REASON_CODE"; + + /** * Key for an extra set on {@code #getDownloadableSubscriptionMetadata} PendingIntent result * callbacks providing the downloadable subscription metadata. */ @@ -491,6 +534,259 @@ public class EuiccManager { @SystemApi public static final int EUICC_OTA_STATUS_UNAVAILABLE = 5; + /** + * List of OperationCode corresponding to {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE}'s + * value, an integer. @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"OPERATION_"}, value = { + OPERATION_SYSTEM, + OPERATION_SIM_SLOT, + OPERATION_EUICC_CARD, + OPERATION_SWITCH, + OPERATION_DOWNLOAD, + OPERATION_METADATA, + OPERATION_EUICC_GSMA, + OPERATION_APDU, + OPERATION_SMDX, + OPERATION_HTTP, + OPERATION_SMDX_SUBJECT_REASON_CODE, + }) + public @interface OperationCode { + } + + /** + * Internal system error. + * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details + */ + public static final int OPERATION_SYSTEM = 1; + + /** + * SIM slot error. Failed to switch slot, failed to access the physical slot etc. + * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details + */ + public static final int OPERATION_SIM_SLOT = 2; + + /** + * eUICC card error. + * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details + */ + public static final int OPERATION_EUICC_CARD = 3; + + /** + * Generic switching profile error + * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details + */ + public static final int OPERATION_SWITCH = 4; + + /** + * Download profile error. + * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details + */ + public static final int OPERATION_DOWNLOAD = 5; + + /** + * Subscription's metadata error + * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details + */ + public static final int OPERATION_METADATA = 6; + + /** + * eUICC returned an error defined in GSMA (SGP.22 v2.2) while running one of the ES10x + * functions. + * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details + */ + public static final int OPERATION_EUICC_GSMA = 7; + + /** + * The exception of failing to execute an APDU command. It can be caused by an error + * happening on opening the basic or logical channel, or the response of the APDU command is + * not success (0x9000). + * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details + */ + public static final int OPERATION_APDU = 8; + + /** + * SMDX(SMDP/SMDS) error + * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details + */ + public static final int OPERATION_SMDX = 9; + + /** + * SubjectCode[5.2.6.1] and ReasonCode[5.2.6.2] error from GSMA (SGP.22 v2.2) + * When {@link #OPERATION_SMDX_SUBJECT_REASON_CODE} is used as the + * {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE}, the remaining three bytes of the integer + * result from {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} will be used to stored the + * SubjectCode and ReasonCode from the GSMA spec and NOT ErrorCode. + * + * The encoding will follow the format of: + * 1. The first byte of the result will be 255(0xFF). + * 2. Remaining three bytes(24 bits) will be split into six sections, 4 bits in each section. + * 3. A SubjectCode/ReasonCode will take 12 bits each. + * 4. The maximum number can be represented per section is 15, as that is the maximum number + * allowed to be stored into 4 bits + * 5. Maximum supported nested category from GSMA is three layers. E.g 8.11.1.2 is not + * supported. + * + * E.g given SubjectCode(8.11.1) and ReasonCode(5.1) + * + * Base10: 0 10 8 11 1 0 5 1 + * Base2: 0000 1010 1000 1011 0001 0000 0101 0001 + * Base16: 0 A 8 B 1 0 5 1 + * + * Thus the integer stored in {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} is + * 0xA8B1051(176885841) + * + * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details + */ + public static final int OPERATION_SMDX_SUBJECT_REASON_CODE = 10; + + /** + * HTTP error + * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details + */ + public static final int OPERATION_HTTP = 11; + + /** + * List of ErrorCode corresponding to {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} + * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"ERROR_"}, value = { + ERROR_CARRIER_LOCKED, + ERROR_INVALID_ACTIVATION_CODE, + ERROR_INVALID_CONFIRMATION_CODE, + ERROR_INCOMPATIBLE_CARRIER, + ERROR_EUICC_INSUFFICIENT_MEMORY, + ERROR_TIME_OUT, + ERROR_EUICC_MISSING, + ERROR_UNSUPPORTED_VERSION, + ERROR_SIM_MISSING, + ERROR_EUICC_GSMA_INSTALL_ERROR, + ERROR_DISALLOWED_BY_PPR, + ERROR_ADDRESS_MISSING, + ERROR_CERTIFICATE_ERROR, + ERROR_NO_PROFILES_AVAILABLE, + ERROR_CONNECTION_ERROR, + ERROR_INVALID_RESPONSE, + ERROR_OPERATION_BUSY, + }) + public @interface ErrorCode{} + + /** + * Operation such as downloading/switching to another profile failed due to device being + * carrier locked. + * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details + */ + public static final int ERROR_CARRIER_LOCKED = 10000; + + /** + * The activation code(SGP.22 v2.2 section[4.1]) is invalid. + * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details + */ + public static final int ERROR_INVALID_ACTIVATION_CODE = 10001; + + /** + * The confirmation code(SGP.22 v2.2 section[4.7]) is invalid. + * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details + */ + public static final int ERROR_INVALID_CONFIRMATION_CODE = 10002; + + /** + * The profile's carrier is incompatible with the LPA. + * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details + */ + public static final int ERROR_INCOMPATIBLE_CARRIER = 10003; + + /** + * There is no more space available on the eUICC for new profiles. + * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details + */ + public static final int ERROR_EUICC_INSUFFICIENT_MEMORY = 10004; + + /** + * Timed out while waiting for an operation to complete. i.e restart, disable, + * switch reset etc. + * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details + */ + public static final int ERROR_TIME_OUT = 10005; + + /** + * eUICC is missing or defective on the device. + * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details + */ + public static final int ERROR_EUICC_MISSING = 10006; + + /** + * The eUICC card(hardware) version is incompatible with the software + * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details + */ + public static final int ERROR_UNSUPPORTED_VERSION = 10007; + + /** + * No SIM card is available in the device. + * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details + */ + public static final int ERROR_SIM_MISSING = 10008; + + /** + * Failure to load the profile onto the eUICC card. i.e + * 1. iccid of the profile already exists on the eUICC. + * 2. GSMA(.22 v2.2) Profile Install Result - installFailedDueToDataMismatch + * 3. operation was interrupted + * 4. SIMalliance error in PEStatus(SGP.22 v2.2 section 2.5.6.1) + * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details + */ + public static final int ERROR_EUICC_GSMA_INSTALL_ERROR = 10009; + + /** + * Failed to load profile onto eUICC due to Profile Poicly Rules. + * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details + */ + public static final int ERROR_DISALLOWED_BY_PPR = 10010; + + + /** + * Address is missing e.g SMDS/SMDP address is missing. + * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details + */ + public static final int ERROR_ADDRESS_MISSING = 10011; + + /** + * Certificate needed for authentication is not valid or missing. E.g SMDP/SMDS authentication + * failed. + * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details + */ + public static final int ERROR_CERTIFICATE_ERROR = 10012; + + + /** + * No profiles available. + * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details + */ + public static final int ERROR_NO_PROFILES_AVAILABLE = 10013; + + /** + * Failure to create a connection. + * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details + */ + public static final int ERROR_CONNECTION_ERROR = 10014; + + /** + * Response format is invalid. e.g SMDP/SMDS response contains invalid json, header or/and ASN1. + * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details + */ + public static final int ERROR_INVALID_RESPONSE = 10015; + + /** + * The operation is currently busy, try again later. + * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details + */ + public static final int ERROR_OPERATION_BUSY = 10016; + private final Context mContext; private int mCardId; diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java index 8c9765b4bf70..9c1be48e247a 100644 --- a/telephony/java/android/telephony/ims/ImsCallProfile.java +++ b/telephony/java/android/telephony/ims/ImsCallProfile.java @@ -328,6 +328,14 @@ public final class ImsCallProfile implements Parcelable { @Deprecated public static final String EXTRA_CALL_RAT_TYPE_ALT = "callRadioTech"; + /** + * String extra property containing forwarded numbers associated with the current connection + * for an IMS call. The value is string array, and it can include multiple numbers, and + * the array values are expected E164 (e.g. +1 (650) 253-0000) format. + */ + public static final String EXTRA_FORWARDED_NUMBER = + "android.telephony.ims.extra.FORWARDED_NUMBER"; + /** @hide */ public int mServiceType; /** @hide */ diff --git a/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java b/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java index 3ec4f3468497..f13371c1d0fa 100644 --- a/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java @@ -17,6 +17,8 @@ package android.telephony.ims.stub; import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; import android.os.Bundle; @@ -206,6 +208,13 @@ public class ImsUtImplBase { return ImsUtImplBase.this.updateCallBarringForServiceClass( cbType, action, barrList, serviceClass); } + + @Override + public int updateCallBarringWithPassword(int cbType, int action, String[] barrList, + int serviceClass, String password) throws RemoteException { + return ImsUtImplBase.this.updateCallBarringWithPassword( + cbType, action, barrList, serviceClass, password); + } }; /** @@ -328,6 +337,14 @@ public class ImsUtImplBase { } /** + * Updates the configuration of the call barring for specified service class with password. + */ + public int updateCallBarringWithPassword(int cbType, int action, @Nullable String[] barrList, + int serviceClass, @NonNull String password) { + return -1; + } + + /** * Updates the configuration of the call forward. */ public int updateCallForward(int action, int condition, String number, int serviceClass, diff --git a/telephony/java/com/android/ims/ImsUtInterface.java b/telephony/java/com/android/ims/ImsUtInterface.java index 15f837189843..4a5380e4551b 100644 --- a/telephony/java/com/android/ims/ImsUtInterface.java +++ b/telephony/java/com/android/ims/ImsUtInterface.java @@ -166,6 +166,12 @@ public interface ImsUtInterface { String[] barrList, int serviceClass); /** + * Modifies the configuration of the call barring for specified service class with password. + */ + public void updateCallBarring(int cbType, int action, Message result, + String[] barrList, int serviceClass, String password); + + /** * Modifies the configuration of the call forward. */ public void updateCallForward(int action, int condition, String number, diff --git a/telephony/java/com/android/ims/internal/IImsUt.aidl b/telephony/java/com/android/ims/internal/IImsUt.aidl index 4f97cc5cfb22..302be65070f7 100644 --- a/telephony/java/com/android/ims/internal/IImsUt.aidl +++ b/telephony/java/com/android/ims/internal/IImsUt.aidl @@ -122,4 +122,10 @@ interface IImsUt { */ int updateCallBarringForServiceClass(int cbType, int action, in String[] barrList, int serviceClass); + + /** + * Updates the configuration of the call barring for specified service class with password. + */ + int updateCallBarringWithPassword(int cbType, int action, in String[] barrList, + int serviceClass, String password); } diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 9b4553957bef..6aa5a52d55d5 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -2186,4 +2186,24 @@ interface ITelephony { int setIccLockEnabled(int subId, boolean enabled, String password); int changeIccLockPassword(int subId, String oldPassword, String newPassword); + + /** + * Request for receiving user activity notification + */ + oneway void requestUserActivityNotification(); + + /** + * Called when userActivity is signalled in the power manager. + * This is safe to call from any thread, with any window manager locks held or not. + */ + oneway void userActivity(); + + /** + * Get the user manual network selection. + * Return empty string if in automatic selection. + * + * @param subId the id of the subscription + * @return operatorinfo on success + */ + String getManualNetworkSelectionPlmn(int subId); } diff --git a/telephony/java/com/android/internal/telephony/OperatorInfo.java b/telephony/java/com/android/internal/telephony/OperatorInfo.java index 64d786391021..2ca459811e04 100644 --- a/telephony/java/com/android/internal/telephony/OperatorInfo.java +++ b/telephony/java/com/android/internal/telephony/OperatorInfo.java @@ -20,6 +20,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; +import android.telephony.AccessNetworkConstants.AccessNetworkType; /** * @hide @@ -43,6 +44,7 @@ public class OperatorInfo implements Parcelable { @UnsupportedAppUsage private State mState = State.UNKNOWN; + private int mRan = AccessNetworkType.UNKNOWN; @UnsupportedAppUsage @@ -69,6 +71,10 @@ public class OperatorInfo implements Parcelable { return mState; } + public int getRan() { + return mRan; + } + @UnsupportedAppUsage OperatorInfo(String operatorAlphaLong, String operatorAlphaShort, @@ -82,6 +88,14 @@ public class OperatorInfo implements Parcelable { mState = state; } + OperatorInfo(String operatorAlphaLong, + String operatorAlphaShort, + String operatorNumeric, + State state, + int ran) { + this (operatorAlphaLong, operatorAlphaShort, operatorNumeric, state); + mRan = ran; + } @UnsupportedAppUsage public OperatorInfo(String operatorAlphaLong, @@ -92,6 +106,14 @@ public class OperatorInfo implements Parcelable { operatorNumeric, rilStateToState(stateString)); } + public OperatorInfo(String operatorAlphaLong, + String operatorAlphaShort, + String operatorNumeric, + int ran) { + this (operatorAlphaLong, operatorAlphaShort, operatorNumeric); + mRan = ran; + } + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public OperatorInfo(String operatorAlphaLong, String operatorAlphaShort, @@ -124,7 +146,8 @@ public class OperatorInfo implements Parcelable { return "OperatorInfo " + mOperatorAlphaLong + "/" + mOperatorAlphaShort + "/" + mOperatorNumeric - + "/" + mState; + + "/" + mState + + "/" + mRan; } /** @@ -150,6 +173,7 @@ public class OperatorInfo implements Parcelable { dest.writeString(mOperatorAlphaShort); dest.writeString(mOperatorNumeric); dest.writeSerializable(mState); + dest.writeInt(mRan); } /** @@ -158,20 +182,21 @@ public class OperatorInfo implements Parcelable { */ @UnsupportedAppUsage public static final Creator<OperatorInfo> CREATOR = - new Creator<OperatorInfo>() { - @Override - public OperatorInfo createFromParcel(Parcel in) { - OperatorInfo opInfo = new OperatorInfo( - in.readString(), /*operatorAlphaLong*/ - in.readString(), /*operatorAlphaShort*/ - in.readString(), /*operatorNumeric*/ - (State) in.readSerializable()); /*state*/ - return opInfo; - } - - @Override - public OperatorInfo[] newArray(int size) { - return new OperatorInfo[size]; - } - }; + new Creator<OperatorInfo>() { + @Override + public OperatorInfo createFromParcel(Parcel in) { + OperatorInfo opInfo = new OperatorInfo( + in.readString(), /*operatorAlphaLong*/ + in.readString(), /*operatorAlphaShort*/ + in.readString(), /*operatorNumeric*/ + (State) in.readSerializable(), /*state*/ + in.readInt()); /*ran*/ + return opInfo; + } + + @Override + public OperatorInfo[] newArray(int size) { + return new OperatorInfo[size]; + } + }; } diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java index a15f73cf348d..48f785091764 100644 --- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java +++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java @@ -180,8 +180,6 @@ public class TelephonyIntents { public static final String ACTION_SIM_STATE_CHANGED = Intent.ACTION_SIM_STATE_CHANGED; - public static final String EXTRA_REBROADCAST_ON_UNLOCK= "rebroadcastOnUnlock"; - /** * <p>Broadcast Action: It indicates the Emergency callback mode blocks datacall/sms * <p class="note">. @@ -214,37 +212,6 @@ public class TelephonyIntents { public static final String SECRET_CODE_ACTION = "android.provider.Telephony.SECRET_CODE"; /** - * Broadcast Action: The Service Provider string(s) have been updated. Activities or - * services that use these strings should update their display. - * The intent will have the following extra values:</p> - * - * <dl> - * <dt>showPlmn</dt><dd>Boolean that indicates whether the PLMN should be shown.</dd> - * <dt>plmn</dt><dd>The operator name of the registered network, as a string.</dd> - * <dt>showSpn</dt><dd>Boolean that indicates whether the SPN should be shown.</dd> - * <dt>spn</dt><dd>The service provider name, as a string.</dd> - * </dl> - * - * Note that <em>showPlmn</em> may indicate that <em>plmn</em> should be displayed, even - * though the value for <em>plmn</em> is null. This can happen, for example, if the phone - * has not registered to a network yet. In this case the receiver may substitute an - * appropriate placeholder string (eg, "No service"). - * - * It is recommended to display <em>plmn</em> before / above <em>spn</em> if - * both are displayed. - * - * <p>Note: this is a protected intent that can only be sent by the system. - */ - public static final String SPN_STRINGS_UPDATED_ACTION = - "android.provider.Telephony.SPN_STRINGS_UPDATED"; - - public static final String EXTRA_SHOW_PLMN = "showPlmn"; - public static final String EXTRA_PLMN = "plmn"; - public static final String EXTRA_SHOW_SPN = "showSpn"; - public static final String EXTRA_SPN = "spn"; - public static final String EXTRA_DATA_SPN = "spnData"; - - /** * <p>Broadcast Action: It indicates one column of a subinfo record has been changed * <p class="note">This is a protected intent that can only be sent * by the system. diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java index 832502cae37d..d0c8024c56fe 100644 --- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java +++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java @@ -148,10 +148,9 @@ public class SmsMessage extends SmsMessageBase { } /** - * Create an SmsMessage from an SMS EF record. + * Creates an SmsMessage from an SMS EF record. * - * @param index Index of SMS record. This should be index in ArrayList - * returned by RuimSmsInterfaceManager.getAllMessagesFromIcc + 1. + * @param index Index of SMS EF record. * @param data Record data. * @return An SmsMessage representing the record. * @@ -202,26 +201,16 @@ public class SmsMessage extends SmsMessageBase { } /** - * TODO(cleanup): why do getSubmitPdu methods take an scAddr input - * and do nothing with it? GSM allows us to specify a SC (eg, - * when responding to an SMS that explicitly requests the response - * is sent to a specific SC), or pass null to use the default - * value. Is there no similar notion in CDMA? Or do we just not - * have it hooked up? - */ - - /** - * Get an SMS-SUBMIT PDU for a destination address and a message + * Gets an SMS-SUBMIT PDU for a destination address and a message. * - * @param scAddr Service Centre address. Null means use default. - * @param destAddr Address of the recipient. - * @param message String representation of the message payload. - * @param statusReportRequested Indicates whether a report is requested for this message. - * @param smsHeader Array containing the data for the User Data Header, preceded - * by the Element Identifiers. - * @return a <code>SubmitPdu</code> containing the encoded SC - * address, if applicable, and the encoded message. - * Returns null on encode error. + * @param scAddr Service Centre address. No use for this message. + * @param destAddr the address of the destination for the message. + * @param message string representation of the message payload. + * @param statusReportRequested indicates whether a report is requested for this message. + * @param smsHeader array containing the data for the User Data Header, preceded by the Element + * Identifiers. + * @return a <code>SubmitPdu</code> containing null SC address and the encoded message. Returns + * null on encode error. * @hide */ @UnsupportedAppUsage @@ -231,18 +220,17 @@ public class SmsMessage extends SmsMessageBase { } /** - * Get an SMS-SUBMIT PDU for a destination address and a message + * Gets an SMS-SUBMIT PDU for a destination address and a message. * - * @param scAddr Service Centre address. Null means use default. - * @param destAddr Address of the recipient. - * @param message String representation of the message payload. - * @param statusReportRequested Indicates whether a report is requested for this message. - * @param smsHeader Array containing the data for the User Data Header, preceded - * by the Element Identifiers. - * @param priority Priority level of the message - * @return a <code>SubmitPdu</code> containing the encoded SC - * address, if applicable, and the encoded message. - * Returns null on encode error. + * @param scAddr Service Centre address. No use for this message. + * @param destAddr the address of the destination for the message. + * @param message string representation of the message payload. + * @param statusReportRequested indicates whether a report is requested for this message. + * @param smsHeader array containing the data for the User Data Header, preceded by the Element + * Identifiers. + * @param priority priority level of the message. + * @return a <code>SubmitPdu</code> containing null SC address and the encoded message. Returns + * null on encode error. * @hide */ @UnsupportedAppUsage @@ -265,16 +253,15 @@ public class SmsMessage extends SmsMessageBase { } /** - * Get an SMS-SUBMIT PDU for a data message to a destination address and port. + * Gets an SMS-SUBMIT PDU for a data message to a destination address & port. * - * @param scAddr Service Centre address. null == use default - * @param destAddr the address of the destination for the message - * @param destPort the port to deliver the message to at the - * destination - * @param data the data for the message - * @return a <code>SubmitPdu</code> containing the encoded SC - * address, if applicable, and the encoded message. - * Returns null on encode error. + * @param scAddr Service Centre address. No use for this message. + * @param destAddr the address of the destination for the message. + * @param destPort the port to deliver the message to at the destination. + * @param data the data for the message. + * @param statusReportRequested indicates whether a report is requested for this message. + * @return a <code>SubmitPdu</code> containing null SC address and the encoded message. Returns + * null on encode error. */ @UnsupportedAppUsage public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, int destPort, @@ -305,14 +292,13 @@ public class SmsMessage extends SmsMessageBase { } /** - * Get an SMS-SUBMIT PDU for a data message to a destination address & port + * Gets an SMS-SUBMIT PDU for a data message to a destination address & port. * - * @param destAddr the address of the destination for the message - * @param userData the data for the message - * @param statusReportRequested Indicates whether a report is requested for this message. - * @return a <code>SubmitPdu</code> containing the encoded SC - * address, if applicable, and the encoded message. - * Returns null on encode error. + * @param destAddr the address of the destination for the message. + * @param userData the data for the message. + * @param statusReportRequested indicates whether a report is requested for this message. + * @return a <code>SubmitPdu</code> containing null SC address and the encoded message. Returns + * null on encode error. */ @UnsupportedAppUsage public static SubmitPdu getSubmitPdu(String destAddr, UserData userData, @@ -321,15 +307,14 @@ public class SmsMessage extends SmsMessageBase { } /** - * Get an SMS-SUBMIT PDU for a data message to a destination address & port + * Gets an SMS-SUBMIT PDU for a data message to a destination address & port. * - * @param destAddr the address of the destination for the message - * @param userData the data for the message - * @param statusReportRequested Indicates whether a report is requested for this message. - * @param priority Priority level of the message - * @return a <code>SubmitPdu</code> containing the encoded SC - * address, if applicable, and the encoded message. - * Returns null on encode error. + * @param destAddr the address of the destination for the message. + * @param userData the data for the message. + * @param statusReportRequested indicates whether a report is requested for this message. + * @param priority Priority level of the message. + * @return a <code>SubmitPdu</code> containing null SC address and the encoded message. Returns + * null on encode error. */ @UnsupportedAppUsage public static SubmitPdu getSubmitPdu(String destAddr, UserData userData, @@ -1059,6 +1044,72 @@ public class SmsMessage extends SmsMessageBase { } /** + * Gets an SMS-DELIVER PDU for a originating address and a message. + * + * @param origAddr the address of the originating for the message. + * @param message string representation of the message payload. + * @param date the time stamp the message was received. + * @return a <code>SubmitPdu</code> containing null SC address and the encoded message. Returns + * null on encode error. + * @hide + */ + public static SubmitPdu getDeliverPdu(String origAddr, String message, long date) { + if (origAddr == null || message == null) { + return null; + } + + CdmaSmsAddress addr = CdmaSmsAddress.parse(origAddr); + if (addr == null) return null; + + BearerData bearerData = new BearerData(); + bearerData.messageType = BearerData.MESSAGE_TYPE_DELIVER; + + bearerData.messageId = 0; + + bearerData.deliveryAckReq = false; + bearerData.userAckReq = false; + bearerData.readAckReq = false; + bearerData.reportReq = false; + + bearerData.userData = new UserData(); + bearerData.userData.payloadStr = message; + + bearerData.msgCenterTimeStamp = BearerData.TimeStamp.fromMillis(date); + + byte[] encodedBearerData = BearerData.encode(bearerData); + if (encodedBearerData == null) return null; + + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(100); + DataOutputStream dos = new DataOutputStream(baos); + dos.writeInt(SmsEnvelope.TELESERVICE_WMT); + dos.writeInt(0); // servicePresent + dos.writeInt(0); // serviceCategory + dos.write(addr.digitMode); + dos.write(addr.numberMode); + dos.write(addr.ton); // number_type + dos.write(addr.numberPlan); + dos.write(addr.numberOfDigits); + dos.write(addr.origBytes, 0, addr.origBytes.length); // digits + // Subaddress is not supported. + dos.write(0); // subaddressType + dos.write(0); // subaddr_odd + dos.write(0); // subaddr_nbr_of_digits + dos.write(encodedBearerData.length); + dos.write(encodedBearerData, 0, encodedBearerData.length); + dos.close(); + + SubmitPdu pdu = new SubmitPdu(); + pdu.encodedMessage = baos.toByteArray(); + pdu.encodedScAddress = null; + return pdu; + } catch (IOException ex) { + Rlog.e(LOG_TAG, "creating Deliver PDU failed: " + ex); + } + return null; + } + + /** * Creates byte array (pseudo pdu) from SMS object. * Note: Do not call this method more than once per object! * @hide diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java index cbf0f5c297e1..6ad6dd119f50 100644 --- a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java +++ b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java @@ -32,6 +32,7 @@ import com.android.internal.telephony.uicc.IccUtils; import com.android.internal.util.BitwiseInputStream; import com.android.internal.util.BitwiseOutputStream; +import java.io.ByteArrayOutputStream; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; @@ -284,6 +285,33 @@ public final class BearerData { return ts; } + public static TimeStamp fromMillis(long timeInMillis) { + TimeStamp ts = new TimeStamp(); + LocalDateTime localDateTime = + Instant.ofEpochMilli(timeInMillis).atZone(ts.mZoneId).toLocalDateTime(); + int year = localDateTime.getYear(); + if (year < 1996 || year > 2095) return null; + ts.year = year; + ts.month = localDateTime.getMonthValue(); + ts.monthDay = localDateTime.getDayOfMonth(); + ts.hour = localDateTime.getHour(); + ts.minute = localDateTime.getMinute(); + ts.second = localDateTime.getSecond(); + return ts; + } + + public byte[] toByteArray() { + int year = this.year % 100; // 00 - 99 + ByteArrayOutputStream outStream = new ByteArrayOutputStream(6); + outStream.write((((year / 10) & 0x0F) << 4) | ((year % 10) & 0x0F)); + outStream.write((((month / 10) << 4) & 0xF0) | ((month % 10) & 0x0F)); + outStream.write((((monthDay / 10) << 4) & 0xF0) | ((monthDay % 10) & 0x0F)); + outStream.write((((hour / 10) << 4) & 0xF0) | ((hour % 10) & 0x0F)); + outStream.write((((minute / 10) << 4) & 0xF0) | ((minute % 10) & 0x0F)); + outStream.write((((second / 10) << 4) & 0xF0) | ((second % 10) & 0x0F)); + return outStream.toByteArray(); + } + public long toMillis() { LocalDateTime localDateTime = LocalDateTime.of(year, month + 1, monthDay, hour, minute, second); @@ -957,6 +985,12 @@ public final class BearerData { } } + private static void encodeMsgCenterTimeStamp(BearerData bData, BitwiseOutputStream outStream) + throws BitwiseOutputStream.AccessException { + outStream.write(8, 6); + outStream.writeByteArray(8 * 6, bData.msgCenterTimeStamp.toByteArray()); + }; + /** * Create serialized representation for BearerData object. * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details) @@ -1021,6 +1055,10 @@ public final class BearerData { outStream.write(8, SUBPARAM_SERVICE_CATEGORY_PROGRAM_RESULTS); encodeScpResults(bData, outStream); } + if (bData.msgCenterTimeStamp != null) { + outStream.write(8, SUBPARAM_MESSAGE_CENTER_TIME_STAMP); + encodeMsgCenterTimeStamp(bData, outStream); + } return outStream.toByteArray(); } catch (BitwiseOutputStream.AccessException ex) { Rlog.e(LOG_TAG, "BearerData encode failed: " + ex); diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java index 417aafd765ea..c91ea696ec29 100644 --- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java +++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java @@ -42,8 +42,11 @@ import com.android.internal.telephony.uicc.IccUtils; import java.io.ByteArrayOutputStream; import java.io.UnsupportedEncodingException; import java.text.ParseException; +import java.time.Instant; import java.time.LocalDateTime; +import java.time.ZoneId; import java.time.ZoneOffset; +import java.time.ZonedDateTime; /** * A Short Message Service message. @@ -167,10 +170,9 @@ public class SmsMessage extends SmsMessageBase { } /** - * Create an SmsMessage from an SMS EF record. + * Creates an SmsMessage from an SMS EF record. * - * @param index Index of SMS record. This should be index in ArrayList - * returned by SmsManager.getAllMessagesFromSim + 1. + * @param index Index of SMS EF record. * @param data Record data. * @return An SmsMessage representing the record. * @@ -259,12 +261,15 @@ public class SmsMessage extends SmsMessageBase { } /** - * Get an SMS-SUBMIT PDU for a destination address and a message + * Gets an SMS-SUBMIT PDU for a destination address and a message. * - * @param scAddress Service Centre address. Null means use default. - * @return a <code>SubmitPdu</code> containing the encoded SC - * address, if applicable, and the encoded message. - * Returns null on encode error. + * @param scAddress Service Centre address. Null means use default. + * @param destinationAddress the address of the destination for the message. + * @param message string representation of the message payload. + * @param statusReportRequested indicates whether a report is reuested for this message. + * @param header a byte array containing the data for the User Data Header. + * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the + * encoded message. Returns null on encode error. * @hide */ @UnsupportedAppUsage @@ -277,17 +282,19 @@ public class SmsMessage extends SmsMessageBase { /** - * Get an SMS-SUBMIT PDU for a destination address and a message using the - * specified encoding. + * Gets an SMS-SUBMIT PDU for a destination address and a message using the specified encoding. * - * @param scAddress Service Centre address. Null means use default. - * @param encoding Encoding defined by constants in - * com.android.internal.telephony.SmsConstants.ENCODING_* + * @param scAddress Service Centre address. Null means use default. + * @param destinationAddress the address of the destination for the message. + * @param message string representation of the message payload. + * @param statusReportRequested indicates whether a report is reuested for this message. + * @param header a byte array containing the data for the User Data Header. + * @param encoding encoding defined by constants in + * com.android.internal.telephony.SmsConstants.ENCODING_* * @param languageTable * @param languageShiftTable - * @return a <code>SubmitPdu</code> containing the encoded SC - * address, if applicable, and the encoded message. - * Returns null on encode error. + * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the + * encoded message. Returns null on encode error. * @hide */ @UnsupportedAppUsage @@ -300,18 +307,20 @@ public class SmsMessage extends SmsMessageBase { } /** - * Get an SMS-SUBMIT PDU for a destination address and a message using the - * specified encoding. + * Gets an SMS-SUBMIT PDU for a destination address and a message using the specified encoding. * - * @param scAddress Service Centre address. Null means use default. - * @param encoding Encoding defined by constants in - * com.android.internal.telephony.SmsConstants.ENCODING_* + * @param scAddress Service Centre address. Null means use default. + * @param destinationAddress the address of the destination for the message. + * @param message string representation of the message payload. + * @param statusReportRequested indicates whether a report is reuested for this message. + * @param header a byte array containing the data for the User Data Header. + * @param encoding encoding defined by constants in + * com.android.internal.telephony.SmsConstants.ENCODING_* * @param languageTable * @param languageShiftTable * @param validityPeriod Validity Period of the message in Minutes. - * @return a <code>SubmitPdu</code> containing the encoded SC - * address, if applicable, and the encoded message. - * Returns null on encode error. + * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the + * encoded message. Returns null on encode error. * @hide */ @UnsupportedAppUsage @@ -483,12 +492,14 @@ public class SmsMessage extends SmsMessageBase { } /** - * Get an SMS-SUBMIT PDU for a destination address and a message + * Gets an SMS-SUBMIT PDU for a destination address and a message. * - * @param scAddress Service Centre address. Null means use default. - * @return a <code>SubmitPdu</code> containing the encoded SC - * address, if applicable, and the encoded message. - * Returns null on encode error. + * @param scAddress Service Centre address. Null means use default. + * @param destinationAddress the address of the destination for the message. + * @param message string representation of the message payload. + * @param statusReportRequested indicates whether a report is reuested for this message. + * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the + * encoded message. Returns null on encode error. */ @UnsupportedAppUsage public static SubmitPdu getSubmitPdu(String scAddress, @@ -499,15 +510,15 @@ public class SmsMessage extends SmsMessageBase { } /** - * Get an SMS-SUBMIT PDU for a destination address and a message + * Gets an SMS-SUBMIT PDU for a destination address and a message. * - * @param scAddress Service Centre address. Null means use default. - * @param destinationAddress the address of the destination for the message - * @param statusReportRequested staus report of the message Requested + * @param scAddress Service Centre address. Null means use default. + * @param destinationAddress the address of the destination for the message. + * @param message string representation of the message payload. + * @param statusReportRequested indicates whether a report is reuested for this message. * @param validityPeriod Validity Period of the message in Minutes. - * @return a <code>SubmitPdu</code> containing the encoded SC - * address, if applicable, and the encoded message. - * Returns null on encode error. + * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the + * encoded message. Returns null on encode error. */ @UnsupportedAppUsage public static SubmitPdu getSubmitPdu(String scAddress, @@ -518,16 +529,15 @@ public class SmsMessage extends SmsMessageBase { } /** - * Get an SMS-SUBMIT PDU for a data message to a destination address & port + * Gets an SMS-SUBMIT PDU for a data message to a destination address & port. * - * @param scAddress Service Centre address. null == use default - * @param destinationAddress the address of the destination for the message - * @param destinationPort the port to deliver the message to at the - * destination - * @param data the data for the message - * @return a <code>SubmitPdu</code> containing the encoded SC - * address, if applicable, and the encoded message. - * Returns null on encode error. + * @param scAddress Service Centre address. Null means use default. + * @param destinationAddress the address of the destination for the message. + * @param destinationPort the port to deliver the message to at the destination. + * @param data the data for the message. + * @param statusReportRequested indicates whether a report is reuested for this message. + * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the + * encoded message. Returns null on encode error. */ public static SubmitPdu getSubmitPdu(String scAddress, String destinationAddress, int destinationPort, byte[] data, @@ -551,8 +561,7 @@ public class SmsMessage extends SmsMessageBase { SubmitPdu ret = new SubmitPdu(); ByteArrayOutputStream bo = getSubmitPduHead( - scAddress, destinationAddress, (byte) 0x41, // MTI = SMS-SUBMIT, - // TP-UDHI = true + scAddress, destinationAddress, (byte) 0x41, /* TP-MTI=SMS-SUBMIT, TP-UDHI=true */ statusReportRequested, ret); // Skip encoding pdu if error occurs when create pdu head and the error will be handled // properly later on encodedMessage sanity check. @@ -579,16 +588,18 @@ public class SmsMessage extends SmsMessageBase { } /** - * Create the beginning of a SUBMIT PDU. This is the part of the - * SUBMIT PDU that is common to the two versions of {@link #getSubmitPdu}, - * one of which takes a byte array and the other of which takes a + * Creates the beginning of a SUBMIT PDU. + * + * This is the part of the SUBMIT PDU that is common to the two versions of + * {@link #getSubmitPdu}, one of which takes a byte array and the other of which takes a * <code>String</code>. * - * @param scAddress Service Centre address. null == use default - * @param destinationAddress the address of the destination for the message + * @param scAddress Service Centre address. Null means use default. + * @param destinationAddress the address of the destination for the message. * @param mtiByte - * @param ret <code>SubmitPdu</code> containing the encoded SC - * address, if applicable, and the encoded message. Returns null on encode error. + * @param statusReportRequested indicates whether a report is reuested for this message. + * @param ret <code>SubmitPdu</code>. + * @return a byte array of the beginning of a SUBMIT PDU. Null for invalid destinationAddress. */ @UnsupportedAppUsage private static ByteArrayOutputStream getSubmitPduHead( @@ -636,6 +647,161 @@ public class SmsMessage extends SmsMessageBase { return bo; } + /** + * Gets an SMS-DELIVER PDU for an originating address and a message. + * + * @param scAddress Service Centre address. Null means use default. + * @param originatingAddress the address of the originating for the message. + * @param message string representation of the message payload. + * @param date the time stamp the message was received. + * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the + * encoded message. Returns null on encode error. + * @hide + */ + public static SubmitPdu getDeliverPdu( + String scAddress, String originatingAddress, String message, long date) { + if (originatingAddress == null || message == null) { + return null; + } + + // Find the best encoding to use + TextEncodingDetails ted = calculateLength(message, false); + int encoding = ted.codeUnitSize; + int languageTable = ted.languageTable; + int languageShiftTable = ted.languageShiftTable; + byte[] header = null; + + if (encoding == ENCODING_7BIT && (languageTable != 0 || languageShiftTable != 0)) { + SmsHeader smsHeader = new SmsHeader(); + smsHeader.languageTable = languageTable; + smsHeader.languageShiftTable = languageShiftTable; + header = SmsHeader.toByteArray(smsHeader); + } + + SubmitPdu ret = new SubmitPdu(); + + ByteArrayOutputStream bo = new ByteArrayOutputStream(MAX_USER_DATA_BYTES + 40); + + // SMSC address with length octet, or 0 + if (scAddress == null) { + ret.encodedScAddress = null; + } else { + ret.encodedScAddress = + PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength(scAddress); + } + + // TP-Message-Type-Indicator + bo.write(0); // SMS-DELIVER + + byte[] oaBytes; + + oaBytes = PhoneNumberUtils.networkPortionToCalledPartyBCD(originatingAddress); + + // Return null for invalid originating address + if (oaBytes == null) return null; + + // Originating address length in BCD digits, ignoring TON byte and pad + // TODO Should be better. + bo.write((oaBytes.length - 1) * 2 - ((oaBytes[oaBytes.length - 1] & 0xf0) == 0xf0 ? 1 : 0)); + + // Originating Address + bo.write(oaBytes, 0, oaBytes.length); + + // TP-Protocol-Identifier + bo.write(0); + + // User Data (and length) + byte[] userData; + try { + if (encoding == ENCODING_7BIT) { + userData = GsmAlphabet.stringToGsm7BitPackedWithHeader(message, header, + languageTable, languageShiftTable); + } else { // Assume UCS-2 + try { + userData = encodeUCS2(message, header); + } catch (UnsupportedEncodingException uex) { + Rlog.e(LOG_TAG, "Implausible UnsupportedEncodingException ", uex); + return null; + } + } + } catch (EncodeException ex) { + if (ex.getError() == EncodeException.ERROR_EXCEED_SIZE) { + Rlog.e(LOG_TAG, "Exceed size limitation EncodeException", ex); + return null; + } else { + // Encoding to the 7-bit alphabet failed. Let's see if we can send it as a UCS-2 + // encoded message + try { + userData = encodeUCS2(message, header); + encoding = ENCODING_16BIT; + } catch (EncodeException ex1) { + Rlog.e(LOG_TAG, "Exceed size limitation EncodeException", ex1); + return null; + } catch (UnsupportedEncodingException uex) { + Rlog.e(LOG_TAG, "Implausible UnsupportedEncodingException ", uex); + return null; + } + } + } + + if (encoding == ENCODING_7BIT) { + if ((0xff & userData[0]) > MAX_USER_DATA_SEPTETS) { + // Message too long + Rlog.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " septets)"); + return null; + } + // TP-Data-Coding-Scheme + // Default encoding, uncompressed + bo.write(0x00); + } else { // Assume UCS-2 + if ((0xff & userData[0]) > MAX_USER_DATA_BYTES) { + // Message too long + Rlog.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " bytes)"); + return null; + } + // TP-Data-Coding-Scheme + // UCS-2 encoding, uncompressed + bo.write(0x08); + } + + // TP-Service-Centre-Time-Stamp + byte[] scts = new byte[7]; + + ZonedDateTime zoneDateTime = Instant.ofEpochMilli(date).atZone(ZoneId.systemDefault()); + LocalDateTime localDateTime = zoneDateTime.toLocalDateTime(); + + // It indicates the difference, expressed in quarters of an hour, between the local time and + // GMT. + int timezoneOffset = zoneDateTime.getOffset().getTotalSeconds() / 60 / 15; + boolean negativeOffset = timezoneOffset < 0; + if (negativeOffset) { + timezoneOffset = -timezoneOffset; + } + int year = localDateTime.getYear(); + int month = localDateTime.getMonthValue(); + int day = localDateTime.getDayOfMonth(); + int hour = localDateTime.getHour(); + int minute = localDateTime.getMinute(); + int second = localDateTime.getSecond(); + + year = year > 2000 ? year - 2000 : year - 1900; + scts[0] = (byte) ((((year % 10) & 0x0F) << 4) | ((year / 10) & 0x0F)); + scts[1] = (byte) ((((month % 10) & 0x0F) << 4) | ((month / 10) & 0x0F)); + scts[2] = (byte) ((((day % 10) & 0x0F) << 4) | ((day / 10) & 0x0F)); + scts[3] = (byte) ((((hour % 10) & 0x0F) << 4) | ((hour / 10) & 0x0F)); + scts[4] = (byte) ((((minute % 10) & 0x0F) << 4) | ((minute / 10) & 0x0F)); + scts[5] = (byte) ((((second % 10) & 0x0F) << 4) | ((second / 10) & 0x0F)); + scts[6] = (byte) ((((timezoneOffset % 10) & 0x0F) << 4) | ((timezoneOffset / 10) & 0x0F)); + if (negativeOffset) { + scts[0] |= 0x08; // Negative offset + } + bo.write(scts, 0, scts.length); + + bo.write(userData, 0, userData.length); + ret.encodedMessage = bo.toByteArray(); + return ret; + } + private static class PduParser { @UnsupportedAppUsage byte mPdu[]; diff --git a/test-mock/api/test-current.txt b/test-mock/api/test-current.txt index cc260ac14147..32ca250b6c74 100644 --- a/test-mock/api/test-current.txt +++ b/test-mock/api/test-current.txt @@ -2,7 +2,6 @@ package android.test.mock { public class MockContext extends android.content.Context { - method public android.view.Display getDisplay(); method public int getDisplayId(); } diff --git a/test-mock/src/android/test/mock/MockContext.java b/test-mock/src/android/test/mock/MockContext.java index 9d913b9861e5..36074edd187e 100644 --- a/test-mock/src/android/test/mock/MockContext.java +++ b/test-mock/src/android/test/mock/MockContext.java @@ -17,6 +17,7 @@ package android.test.mock; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.app.IApplicationThread; import android.app.IServiceConnection; @@ -811,6 +812,11 @@ public class MockContext extends Context { } @Override + public @NonNull Context createWindowContext(int type) { + throw new UnsupportedOperationException(); + } + + @Override public boolean isRestricted() { throw new UnsupportedOperationException(); } @@ -821,7 +827,6 @@ public class MockContext extends Context { throw new UnsupportedOperationException(); } - /** @hide */ @Override public Display getDisplay() { throw new UnsupportedOperationException(); diff --git a/tests/ApkVerityTest/block_device_writer/Android.bp b/tests/ApkVerityTest/block_device_writer/Android.bp index e7a63e414172..78850c534596 100644 --- a/tests/ApkVerityTest/block_device_writer/Android.bp +++ b/tests/ApkVerityTest/block_device_writer/Android.bp @@ -30,7 +30,23 @@ cc_test { "-g", ], shared_libs: ["libbase", "libutils"], + // For some reasons, cuttlefish (x86) uses x86_64 test suites for testing. Unfortunately, when + // the uploader does not pick up the executable from correct output location. The following + // workaround allows the test to: + // * upload the 32-bit exectuable for both 32 and 64 bits devices to use + // * refer to the same executable name in Java + // * no need to force the Java test to be archiecture specific. + // + // See b/145573317 for details. + multilib: { + lib32: { + suffix: "", + }, + lib64: { + suffix: "64", // not really used + }, + }, - test_suites: ["general-tests"], + test_suites: ["general-tests", "pts"], gtest: false, } diff --git a/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java b/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java index 592aa3ac4a6b..153ca79e346b 100644 --- a/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java +++ b/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java @@ -58,4 +58,31 @@ public class WallpaperServiceTest { ambientModeChangedCount[0], 2); } + @Test + public void testDeliversZoomChanged() { + int[] zoomChangedCount = {0}; + WallpaperService service = new WallpaperService() { + @Override + public Engine onCreateEngine() { + return new Engine() { + @Override + public void onZoomChanged(float zoom) { + super.onZoomChanged(zoom); + zoomChangedCount[0]++; + } + }; + } + }; + WallpaperService.Engine engine = service.onCreateEngine(); + engine.setCreated(true); + + engine.setZoom(.5f); + assertEquals("engine scale was not updated", .5f, engine.getZoom(), .001f); + assertEquals("onZoomChanged should have been called", 1, zoomChangedCount[0]); + + engine.setZoom(0); + assertEquals("engine scale was not updated", 0, engine.getZoom(), .001f); + assertEquals("onAmbientModeChanged should have been called", 2, zoomChangedCount[0]); + } + } diff --git a/tests/SoundTriggerTests/src/android/hardware/soundtrigger/SoundTriggerTest.java b/tests/SoundTriggerTests/src/android/hardware/soundtrigger/SoundTriggerTest.java index 65a3d8a337db..c900eaedbdae 100644 --- a/tests/SoundTriggerTests/src/android/hardware/soundtrigger/SoundTriggerTest.java +++ b/tests/SoundTriggerTests/src/android/hardware/soundtrigger/SoundTriggerTest.java @@ -30,6 +30,7 @@ import android.test.suitebuilder.annotation.LargeTest; import android.test.suitebuilder.annotation.SmallTest; import java.util.Arrays; +import java.util.Locale; import java.util.Random; import java.util.UUID; @@ -38,7 +39,8 @@ public class SoundTriggerTest extends InstrumentationTestCase { @SmallTest public void testKeyphraseParcelUnparcel_noUsers() throws Exception { - Keyphrase keyphrase = new Keyphrase(1, 0, "en-US", "hello", null); + Keyphrase keyphrase = new Keyphrase(1, 0, + Locale.forLanguageTag("en-US"), "hello", null); // Write to a parcel Parcel parcel = Parcel.obtain(); @@ -57,7 +59,8 @@ public class SoundTriggerTest extends InstrumentationTestCase { @SmallTest public void testKeyphraseParcelUnparcel_zeroUsers() throws Exception { - Keyphrase keyphrase = new Keyphrase(1, 0, "en-US", "hello", new int[0]); + Keyphrase keyphrase = new Keyphrase(1, 0, + Locale.forLanguageTag("en-US"), "hello", new int[0]); // Write to a parcel Parcel parcel = Parcel.obtain(); @@ -76,7 +79,8 @@ public class SoundTriggerTest extends InstrumentationTestCase { @SmallTest public void testKeyphraseParcelUnparcel_pos() throws Exception { - Keyphrase keyphrase = new Keyphrase(1, 0, "en-US", "hello", new int[] {1, 2, 3, 4, 5}); + Keyphrase keyphrase = new Keyphrase(1, 0, + Locale.forLanguageTag("en-US"), "hello", new int[] {1, 2, 3, 4, 5}); // Write to a parcel Parcel parcel = Parcel.obtain(); @@ -96,8 +100,10 @@ public class SoundTriggerTest extends InstrumentationTestCase { @SmallTest public void testKeyphraseSoundModelParcelUnparcel_noData() throws Exception { Keyphrase[] keyphrases = new Keyphrase[2]; - keyphrases[0] = new Keyphrase(1, 0, "en-US", "hello", new int[] {0}); - keyphrases[1] = new Keyphrase(2, 0, "fr-FR", "there", new int[] {1, 2}); + keyphrases[0] = new Keyphrase(1, 0, Locale.forLanguageTag("en-US"), + "hello", new int[] {0}); + keyphrases[1] = new Keyphrase(2, 0, Locale.forLanguageTag("fr-FR"), + "there", new int[] {1, 2}); KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), UUID.randomUUID(), null, keyphrases); @@ -119,8 +125,10 @@ public class SoundTriggerTest extends InstrumentationTestCase { @SmallTest public void testKeyphraseSoundModelParcelUnparcel_zeroData() throws Exception { Keyphrase[] keyphrases = new Keyphrase[2]; - keyphrases[0] = new Keyphrase(1, 0, "en-US", "hello", new int[] {0}); - keyphrases[1] = new Keyphrase(2, 0, "fr-FR", "there", new int[] {1, 2}); + keyphrases[0] = new Keyphrase(1, 0, Locale.forLanguageTag("en-US"), + "hello", new int[] {0}); + keyphrases[1] = new Keyphrase(2, 0, Locale.forLanguageTag("fr-FR"), + "there", new int[] {1, 2}); KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), UUID.randomUUID(), new byte[0], keyphrases); @@ -186,8 +194,10 @@ public class SoundTriggerTest extends InstrumentationTestCase { @LargeTest public void testKeyphraseSoundModelParcelUnparcel_largeData() throws Exception { Keyphrase[] keyphrases = new Keyphrase[2]; - keyphrases[0] = new Keyphrase(1, 0, "en-US", "hello", new int[] {0}); - keyphrases[1] = new Keyphrase(2, 0, "fr-FR", "there", new int[] {1, 2}); + keyphrases[0] = new Keyphrase(1, 0, Locale.forLanguageTag("en-US"), + "hello", new int[] {0}); + keyphrases[1] = new Keyphrase(2, 0, Locale.forLanguageTag("fr-FR"), + "there", new int[] {1, 2}); byte[] data = new byte[200 * 1024]; mRandom.nextBytes(data); KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), UUID.randomUUID(), diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java index 6687f83ad0db..61696718c76e 100644 --- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java +++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java @@ -46,15 +46,15 @@ public class SurfaceControlViewHostTest extends Activity implements SurfaceHolde mView.setZOrderOnTop(true); mView.getHolder().addCallback(this); + + addEmbeddedView(); } - @Override - public void surfaceCreated(SurfaceHolder holder) { + void addEmbeddedView() { mVr = new SurfaceControlViewHost(this, this.getDisplay(), - mView.getInputToken()); + mView.getHostToken()); - final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - t.reparent(mVr.getSurfacePackage().getSurfaceControl(), mView.getSurfaceControl()).apply(); + mView.setChildSurfacePackage(mVr.getSurfacePackage()); Button v = new Button(this); v.setBackgroundColor(Color.BLUE); @@ -70,6 +70,10 @@ public class SurfaceControlViewHostTest extends Activity implements SurfaceHolde } @Override + public void surfaceCreated(SurfaceHolder holder) { + } + + @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { Canvas canvas = holder.lockCanvas(); canvas.drawColor(Color.GREEN); diff --git a/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java b/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java index 54c944f9588e..b357ad076c11 100644 --- a/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java +++ b/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java @@ -16,9 +16,6 @@ package com.android.test.voiceenrollment; -import java.util.Random; -import java.util.UUID; - import android.app.Activity; import android.hardware.soundtrigger.SoundTrigger; import android.hardware.soundtrigger.SoundTrigger.Keyphrase; @@ -29,6 +26,13 @@ import android.util.Log; import android.view.View; import android.widget.Toast; +import java.util.Locale; +import java.util.Random; +import java.util.UUID; + +/** + * TODO: must be transitioned to a service. + */ public class TestEnrollmentActivity extends Activity { private static final String TAG = "TestEnrollmentActivity"; private static final boolean DBG = false; @@ -56,7 +60,8 @@ public class TestEnrollmentActivity extends Activity { * Performs a fresh enrollment. */ public void onEnrollButtonClicked(View v) { - Keyphrase kp = new Keyphrase(KEYPHRASE_ID, RECOGNITION_MODES, BCP47_LOCALE, TEXT, + Keyphrase kp = new Keyphrase(KEYPHRASE_ID, RECOGNITION_MODES, + Locale.forLanguageTag(BCP47_LOCALE), TEXT, new int[] { UserManager.get(this).getUserHandle() /* current user */}); UUID modelUuid = UUID.randomUUID(); // Generate a fake model to push. diff --git a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java index f3c89d8addf6..01e212d01574 100644 --- a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java +++ b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java @@ -22,6 +22,7 @@ import android.animation.ValueAnimator; import android.app.Activity; import android.graphics.Insets; import android.os.Bundle; +import android.util.Log; import android.util.Property; import android.view.View; import android.view.WindowInsets; @@ -67,8 +68,8 @@ public class WindowInsetsActivity extends Activity { } }; - float showY; - float hideY; + float startY; + float endY; InsetsAnimation imeAnim; @Override @@ -84,16 +85,6 @@ public class WindowInsetsActivity extends Activity { v.getWindowInsetsController().hide(Type.ime()); } }); - mRoot.getViewTreeObserver().addOnGlobalLayoutListener(() -> { - if (imeAnim == null) { - return; - } - if (mRoot.getRootWindowInsets().isVisible(Type.ime())) { - showY = mButton.getTop(); - } else { - hideY = mButton.getTop(); - } - }); mRoot.setWindowInsetsAnimationCallback(new WindowInsetsAnimationCallback() { @Override @@ -106,22 +97,19 @@ public class WindowInsetsActivity extends Activity { if ((animation.getTypeMask() & Type.ime()) != 0) { imeAnim = animation; } - if (mRoot.getRootWindowInsets().isVisible(Type.ime())) { - showY = mButton.getTop(); - } else { - hideY = mButton.getTop(); - } + startY = mButton.getTop(); } @Override public WindowInsets onProgress(WindowInsets insets) { - mButton.setY(hideY + (showY - hideY) * imeAnim.getInterpolatedFraction()); + mButton.setY(startY + (endY - startY) * imeAnim.getInterpolatedFraction()); return insets; } @Override public AnimationBounds onStart(InsetsAnimation animation, AnimationBounds bounds) { + endY = mButton.getTop(); return bounds; } diff --git a/tests/net/common/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java index 6005cc375d5c..f25fd4daf829 100644 --- a/tests/net/common/java/android/net/LinkPropertiesTest.java +++ b/tests/net/common/java/android/net/LinkPropertiesTest.java @@ -75,6 +75,9 @@ public class LinkPropertiesTest { private static final LinkAddress LINKADDRV4 = new LinkAddress(ADDRV4, 32); private static final LinkAddress LINKADDRV6 = new LinkAddress(ADDRV6, 128); private static final LinkAddress LINKADDRV6LINKLOCAL = new LinkAddress("fe80::1/64"); + private static final Uri CAPPORT_API_URL = Uri.parse("https://test.example.com/capportapi"); + private static final CaptivePortalData CAPPORT_DATA = new CaptivePortalData.Builder() + .setVenueInfoUrl(Uri.parse("https://test.example.com/venue")).build(); private static InetAddress address(String addrString) { return InetAddresses.parseNumericAddress(addrString); @@ -101,6 +104,8 @@ public class LinkPropertiesTest { assertFalse(lp.isIpv6Provisioned()); assertFalse(lp.isPrivateDnsActive()); assertFalse(lp.isWakeOnLanSupported()); + assertNull(lp.getCaptivePortalApiUrl()); + assertNull(lp.getCaptivePortalData()); } private LinkProperties makeTestObject() { @@ -124,6 +129,8 @@ public class LinkPropertiesTest { lp.setNat64Prefix(new IpPrefix("2001:db8:0:64::/96")); lp.setDhcpServerAddress(DHCPSERVER); lp.setWakeOnLanSupported(true); + lp.setCaptivePortalApiUrl(CAPPORT_API_URL); + lp.setCaptivePortalData(CAPPORT_DATA); return lp; } @@ -165,6 +172,12 @@ public class LinkPropertiesTest { assertTrue(source.isIdenticalWakeOnLan(target)); assertTrue(target.isIdenticalWakeOnLan(source)); + assertTrue(source.isIdenticalCaptivePortalApiUrl(target)); + assertTrue(target.isIdenticalCaptivePortalApiUrl(source)); + + assertTrue(source.isIdenticalCaptivePortalData(target)); + assertTrue(target.isIdenticalCaptivePortalData(source)); + // Check result of equals(). assertTrue(source.equals(target)); assertTrue(target.equals(source)); @@ -963,6 +976,8 @@ public class LinkPropertiesTest { source.setNat64Prefix(new IpPrefix("2001:db8:1:2:64:64::/96")); source.setWakeOnLanSupported(true); + source.setCaptivePortalApiUrl(CAPPORT_API_URL); + source.setCaptivePortalData(CAPPORT_DATA); source.setDhcpServerAddress((Inet4Address) GATEWAY1); @@ -970,7 +985,13 @@ public class LinkPropertiesTest { stacked.setInterfaceName("test-stacked"); source.addStackedLink(stacked); - assertParcelSane(source, 16 /* fieldCount */); + assertParcelSane(source.makeSensitiveFieldsParcelingCopy(), 18 /* fieldCount */); + + // Verify that without using a sensitiveFieldsParcelingCopy, sensitive fields are cleared. + final LinkProperties sanitized = new LinkProperties(source); + sanitized.setCaptivePortalApiUrl(null); + sanitized.setCaptivePortalData(null); + assertEquals(sanitized, parcelingRoundTrip(source)); } @Test @@ -1113,4 +1134,22 @@ public class LinkPropertiesTest { lp.clear(); assertFalse(lp.isWakeOnLanSupported()); } + + @Test + public void testCaptivePortalApiUrl() { + final LinkProperties lp = makeTestObject(); + assertEquals(CAPPORT_API_URL, lp.getCaptivePortalApiUrl()); + + lp.clear(); + assertNull(lp.getCaptivePortalApiUrl()); + } + + @Test + public void testCaptivePortalData() { + final LinkProperties lp = makeTestObject(); + assertEquals(CAPPORT_DATA, lp.getCaptivePortalData()); + + lp.clear(); + assertNull(lp.getCaptivePortalData()); + } } diff --git a/tests/net/java/android/net/CaptivePortalDataTest.kt b/tests/net/java/android/net/CaptivePortalDataTest.kt new file mode 100644 index 000000000000..00714382684f --- /dev/null +++ b/tests/net/java/android/net/CaptivePortalDataTest.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 android.net + +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.testutils.assertParcelSane +import com.android.testutils.assertParcelingIsLossless +import org.junit.Test +import org.junit.runner.RunWith +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals + +@SmallTest +@RunWith(AndroidJUnit4::class) +class CaptivePortalDataTest { + private val data = CaptivePortalData.Builder() + .setRefreshTime(123L) + .setUserPortalUrl(Uri.parse("https://portal.example.com/test")) + .setVenueInfoUrl(Uri.parse("https://venue.example.com/test")) + .setSessionExtendable(true) + .setBytesRemaining(456L) + .setExpiryTime(789L) + .setCaptive(true) + .build() + + private fun makeBuilder() = CaptivePortalData.Builder(data) + + @Test + fun testParcelUnparcel() { + assertParcelSane(data, fieldCount = 7) + + assertParcelingIsLossless(makeBuilder().setUserPortalUrl(null).build()) + assertParcelingIsLossless(makeBuilder().setVenueInfoUrl(null).build()) + } + + @Test + fun testEquals() { + assertEquals(data, makeBuilder().build()) + + assertNotEqualsAfterChange { it.setRefreshTime(456L) } + assertNotEqualsAfterChange { it.setUserPortalUrl(Uri.parse("https://example.com/")) } + assertNotEqualsAfterChange { it.setUserPortalUrl(null) } + assertNotEqualsAfterChange { it.setVenueInfoUrl(Uri.parse("https://example.com/")) } + assertNotEqualsAfterChange { it.setVenueInfoUrl(null) } + assertNotEqualsAfterChange { it.setSessionExtendable(false) } + assertNotEqualsAfterChange { it.setBytesRemaining(789L) } + assertNotEqualsAfterChange { it.setExpiryTime(12L) } + assertNotEqualsAfterChange { it.setCaptive(false) } + } + + private fun CaptivePortalData.mutate(mutator: (CaptivePortalData.Builder) -> Unit) = + CaptivePortalData.Builder(this).apply { mutator(this) }.build() + + private fun assertNotEqualsAfterChange(mutator: (CaptivePortalData.Builder) -> Unit) { + assertNotEquals(data, data.mutate(mutator)) + } +}
\ No newline at end of file diff --git a/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java new file mode 100644 index 000000000000..065add4fc253 --- /dev/null +++ b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java @@ -0,0 +1,196 @@ +/* + * 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 static android.net.ConnectivityDiagnosticsManager.ConnectivityReport; +import static android.net.ConnectivityDiagnosticsManager.DataStallReport; + +import static com.android.testutils.ParcelUtilsKt.assertParcelSane; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import android.os.PersistableBundle; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ConnectivityDiagnosticsManagerTest { + private static final int NET_ID = 1; + private static final int DETECTION_METHOD = 2; + private static final long TIMESTAMP = 10L; + private static final String INTERFACE_NAME = "interface"; + private static final String BUNDLE_KEY = "key"; + private static final String BUNDLE_VALUE = "value"; + + private ConnectivityReport createSampleConnectivityReport() { + final LinkProperties linkProperties = new LinkProperties(); + linkProperties.setInterfaceName(INTERFACE_NAME); + + final NetworkCapabilities networkCapabilities = new NetworkCapabilities(); + networkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_IMS); + + final PersistableBundle bundle = new PersistableBundle(); + bundle.putString(BUNDLE_KEY, BUNDLE_VALUE); + + return new ConnectivityReport( + new Network(NET_ID), TIMESTAMP, linkProperties, networkCapabilities, bundle); + } + + private ConnectivityReport createDefaultConnectivityReport() { + return new ConnectivityReport( + new Network(0), + 0L, + new LinkProperties(), + new NetworkCapabilities(), + PersistableBundle.EMPTY); + } + + @Test + public void testPersistableBundleEquals() { + assertFalse( + ConnectivityDiagnosticsManager.persistableBundleEquals( + null, PersistableBundle.EMPTY)); + assertFalse( + ConnectivityDiagnosticsManager.persistableBundleEquals( + PersistableBundle.EMPTY, null)); + assertTrue( + ConnectivityDiagnosticsManager.persistableBundleEquals( + PersistableBundle.EMPTY, PersistableBundle.EMPTY)); + + final PersistableBundle a = new PersistableBundle(); + a.putString(BUNDLE_KEY, BUNDLE_VALUE); + + final PersistableBundle b = new PersistableBundle(); + b.putString(BUNDLE_KEY, BUNDLE_VALUE); + + final PersistableBundle c = new PersistableBundle(); + c.putString(BUNDLE_KEY, null); + + assertFalse( + ConnectivityDiagnosticsManager.persistableBundleEquals(PersistableBundle.EMPTY, a)); + assertFalse( + ConnectivityDiagnosticsManager.persistableBundleEquals(a, PersistableBundle.EMPTY)); + + assertTrue(ConnectivityDiagnosticsManager.persistableBundleEquals(a, b)); + assertTrue(ConnectivityDiagnosticsManager.persistableBundleEquals(b, a)); + + assertFalse(ConnectivityDiagnosticsManager.persistableBundleEquals(a, c)); + assertFalse(ConnectivityDiagnosticsManager.persistableBundleEquals(c, a)); + } + + @Test + public void testConnectivityReportEquals() { + assertEquals(createSampleConnectivityReport(), createSampleConnectivityReport()); + assertEquals(createDefaultConnectivityReport(), createDefaultConnectivityReport()); + + final LinkProperties linkProperties = new LinkProperties(); + linkProperties.setInterfaceName(INTERFACE_NAME); + + final NetworkCapabilities networkCapabilities = new NetworkCapabilities(); + networkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_IMS); + + final PersistableBundle bundle = new PersistableBundle(); + bundle.putString(BUNDLE_KEY, BUNDLE_VALUE); + + assertNotEquals( + createDefaultConnectivityReport(), + new ConnectivityReport( + new Network(NET_ID), + 0L, + new LinkProperties(), + new NetworkCapabilities(), + PersistableBundle.EMPTY)); + assertNotEquals( + createDefaultConnectivityReport(), + new ConnectivityReport( + new Network(0), + TIMESTAMP, + new LinkProperties(), + new NetworkCapabilities(), + PersistableBundle.EMPTY)); + assertNotEquals( + createDefaultConnectivityReport(), + new ConnectivityReport( + new Network(0), + 0L, + linkProperties, + new NetworkCapabilities(), + PersistableBundle.EMPTY)); + assertNotEquals( + createDefaultConnectivityReport(), + new ConnectivityReport( + new Network(0), + TIMESTAMP, + new LinkProperties(), + networkCapabilities, + PersistableBundle.EMPTY)); + assertNotEquals( + createDefaultConnectivityReport(), + new ConnectivityReport( + new Network(0), + TIMESTAMP, + new LinkProperties(), + new NetworkCapabilities(), + bundle)); + } + + @Test + public void testConnectivityReportParcelUnparcel() { + assertParcelSane(createSampleConnectivityReport(), 5); + } + + private DataStallReport createSampleDataStallReport() { + final PersistableBundle bundle = new PersistableBundle(); + bundle.putString(BUNDLE_KEY, BUNDLE_VALUE); + return new DataStallReport(new Network(NET_ID), TIMESTAMP, DETECTION_METHOD, bundle); + } + + private DataStallReport createDefaultDataStallReport() { + return new DataStallReport(new Network(0), 0L, 0, PersistableBundle.EMPTY); + } + + @Test + public void testDataStallReportEquals() { + assertEquals(createSampleDataStallReport(), createSampleDataStallReport()); + assertEquals(createDefaultDataStallReport(), createDefaultDataStallReport()); + + final PersistableBundle bundle = new PersistableBundle(); + bundle.putString(BUNDLE_KEY, BUNDLE_VALUE); + + assertNotEquals( + createDefaultDataStallReport(), + new DataStallReport(new Network(NET_ID), 0L, 0, PersistableBundle.EMPTY)); + assertNotEquals( + createDefaultDataStallReport(), + new DataStallReport(new Network(0), TIMESTAMP, 0, PersistableBundle.EMPTY)); + assertNotEquals( + createDefaultDataStallReport(), + new DataStallReport(new Network(0), 0L, DETECTION_METHOD, PersistableBundle.EMPTY)); + assertNotEquals( + createDefaultDataStallReport(), new DataStallReport(new Network(0), 0L, 0, bundle)); + } + + @Test + public void testDataStallReportParcelUnparcel() { + assertParcelSane(createSampleDataStallReport(), 4); + } +} diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 4e2933472706..09cc69e83f41 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -21,6 +21,8 @@ import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS; import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED; import static android.content.pm.PackageManager.GET_PERMISSIONS; import static android.content.pm.PackageManager.MATCH_ANY_USER; +import static android.content.pm.PackageManager.PERMISSION_DENIED; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_SUPL; @@ -114,6 +116,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import android.Manifest; import android.annotation.NonNull; import android.app.AlarmManager; import android.app.NotificationManager; @@ -129,6 +132,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.content.res.Resources; +import android.net.CaptivePortalData; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.ConnectivityManager.PacketKeepalive; @@ -165,6 +169,7 @@ import android.net.ResolverParamsParcel; import android.net.RouteInfo; import android.net.SocketKeepalive; import android.net.UidRange; +import android.net.Uri; import android.net.metrics.IpConnectivityLog; import android.net.shared.NetworkMonitorUtils; import android.net.shared.PrivateDnsConfig; @@ -243,8 +248,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; @@ -347,6 +354,8 @@ public class ConnectivityServiceTest { @Spy private Resources mResources; private final LinkedBlockingQueue<Intent> mStartedActivities = new LinkedBlockingQueue<>(); + // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant + private final HashMap<String, Integer> mMockedPermissions = new HashMap<>(); MockContext(Context base, ContentProvider settingsProvider) { super(base); @@ -417,13 +426,39 @@ public class ConnectivityServiceTest { } @Override + public int checkPermission(String permission, int pid, int uid) { + final Integer granted = mMockedPermissions.get(permission); + if (granted == null) { + // All non-mocked permissions should be held by the test or unnecessary: check as + // normal to make sure the code does not rely on unexpected permissions. + return super.checkPermission(permission, pid, uid); + } + return granted; + } + + @Override public void enforceCallingOrSelfPermission(String permission, String message) { - // The mainline permission can only be held if signed with the network stack certificate - // Skip testing for this permission. - if (NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK.equals(permission)) return; - // All other permissions should be held by the test or unnecessary: check as normal to - // make sure the code does not rely on unexpected permissions. - super.enforceCallingOrSelfPermission(permission, message); + final Integer granted = mMockedPermissions.get(permission); + if (granted == null) { + super.enforceCallingOrSelfPermission(permission, message); + return; + } + + if (!granted.equals(PERMISSION_GRANTED)) { + throw new SecurityException("[Test] permission denied: " + permission); + } + } + + /** + * Mock checks for the specified permission, and have them behave as per {@code granted}. + * + * <p>Passing null reverts to default behavior, which does a real permission check on the + * test package. + * @param granted One of {@link PackageManager#PERMISSION_GRANTED} or + * {@link PackageManager#PERMISSION_DENIED}. + */ + public void setPermission(String permission, Integer granted) { + mMockedPermissions.put(permission, granted); } @Override @@ -1750,6 +1785,66 @@ public class ConnectivityServiceTest { assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); } + private void doNetworkCallbacksSanitizationTest(boolean sanitized) throws Exception { + final TestNetworkCallback callback = new TestNetworkCallback(); + final TestNetworkCallback defaultCallback = new TestNetworkCallback(); + final NetworkRequest wifiRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_WIFI).build(); + mCm.registerNetworkCallback(wifiRequest, callback); + mCm.registerDefaultNetworkCallback(defaultCallback); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(false); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + + final LinkProperties newLp = new LinkProperties(); + final Uri capportUrl = Uri.parse("https://capport.example.com/api"); + final CaptivePortalData capportData = new CaptivePortalData.Builder() + .setCaptive(true).build(); + newLp.setCaptivePortalApiUrl(capportUrl); + newLp.setCaptivePortalData(capportData); + mWiFiNetworkAgent.sendLinkProperties(newLp); + + final Uri expectedCapportUrl = sanitized ? null : capportUrl; + final CaptivePortalData expectedCapportData = sanitized ? null : capportData; + callback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp -> + Objects.equals(expectedCapportUrl, lp.getCaptivePortalApiUrl()) + && Objects.equals(expectedCapportData, lp.getCaptivePortalData())); + defaultCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp -> + Objects.equals(expectedCapportUrl, lp.getCaptivePortalApiUrl()) + && Objects.equals(expectedCapportData, lp.getCaptivePortalData())); + + final LinkProperties lp = mCm.getLinkProperties(mWiFiNetworkAgent.getNetwork()); + assertEquals(expectedCapportUrl, lp.getCaptivePortalApiUrl()); + assertEquals(expectedCapportData, lp.getCaptivePortalData()); + } + + @Test + public void networkCallbacksSanitizationTest_Sanitize() throws Exception { + mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + PERMISSION_DENIED); + mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS, + PERMISSION_DENIED); + doNetworkCallbacksSanitizationTest(true /* sanitized */); + } + + @Test + public void networkCallbacksSanitizationTest_NoSanitize_NetworkStack() throws Exception { + mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + PERMISSION_GRANTED); + mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS, PERMISSION_DENIED); + doNetworkCallbacksSanitizationTest(false /* sanitized */); + } + + @Test + public void networkCallbacksSanitizationTest_NoSanitize_Settings() throws Exception { + mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + PERMISSION_DENIED); + mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED); + doNetworkCallbacksSanitizationTest(false /* sanitized */); + } + @Test public void testMultipleLingering() throws Exception { // This test would be flaky with the default 120ms timer: that is short enough that @@ -2628,6 +2723,8 @@ public class ConnectivityServiceTest { final String testKey = "testkey"; final String testValue = "testvalue"; testBundle.putString(testKey, testValue); + mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + PERMISSION_GRANTED); mCm.startCaptivePortalApp(wifiNetwork, testBundle); final Intent signInIntent = mServiceContext.expectStartActivityIntent(TIMEOUT_MS); assertEquals(ACTION_CAPTIVE_PORTAL_SIGN_IN, signInIntent.getAction()); diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java index 8eac3ea13a23..fe0224a27c80 100644 --- a/tests/testables/src/android/testing/TestableLooper.java +++ b/tests/testables/src/android/testing/TestableLooper.java @@ -234,7 +234,9 @@ public class TestableLooper { try { mLooper = setAsMain ? Looper.getMainLooper() : createLooper(); mTestableLooper = new TestableLooper(mLooper, false); - mTestableLooper.getLooper().getThread().setName(test.getClass().getName()); + if (!setAsMain) { + mTestableLooper.getLooper().getThread().setName(test.getClass().getName()); + } } catch (Exception e) { throw new RuntimeException(e); } diff --git a/wifi/Android.bp b/wifi/Android.bp index 8fc8af980f9e..dae04c6c3a25 100644 --- a/wifi/Android.bp +++ b/wifi/Android.bp @@ -91,6 +91,8 @@ java_library { srcs: [ ":framework-wifi-updatable-sources", ], + // java_api_finder must accompany `srcs` + plugins: ["java_api_finder"], installable: false, visibility: [ "//frameworks/opt/net/wifi/service", @@ -128,7 +130,6 @@ java_library { "com.android.wifi", "test_com.android.wifi", ], - plugins: ["java_api_finder"], } droidstubs { diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index 0b5969a8999d..62bb2db69f39 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -88,6 +88,8 @@ interface IWifiManager boolean disableNetwork(int netId, String packageName); + void allowAutojoinGlobal(boolean choice); + void allowAutojoin(int netId, boolean choice); void allowAutojoinPasspoint(String fqdn, boolean enableAutoJoin); @@ -179,8 +181,6 @@ interface IWifiManager int getVerboseLoggingLevel(); - void enableWifiConnectivityManager(boolean enabled); - void disableEphemeralNetwork(String SSID, String packageName); void factoryReset(String packageName); @@ -260,4 +260,9 @@ interface IWifiManager boolean setWifiConnectedNetworkScorer(in IBinder binder, in IWifiConnectedNetworkScorer scorer); void clearWifiConnectedNetworkScorer(); + + /** + * Return the Map of {@link WifiNetworkSuggestion} and the list of <ScanResult> + */ + Map getMatchingScanResults(in List<WifiNetworkSuggestion> networkSuggestions, in List<ScanResult> scanResults, String callingPackage, String callingFeatureId); } diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java index e2befeb29004..114e0fa21296 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -1662,7 +1662,9 @@ public class WifiConfiguration implements Parcelable { */ @NonNull public NetworkSelectionStatus build() { - return mNetworkSelectionStatus; + NetworkSelectionStatus status = new NetworkSelectionStatus(); + status.copy(mNetworkSelectionStatus); + return status; } } diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java index f2a875b43538..419bcb107013 100644 --- a/wifi/java/android/net/wifi/WifiInfo.java +++ b/wifi/java/android/net/wifi/WifiInfo.java @@ -435,7 +435,7 @@ public class WifiInfo implements Parcelable { */ @NonNull public WifiInfo build() { - return mWifiInfo; + return new WifiInfo(mWifiInfo); } } diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 208ce9c37b23..f1ebf6b50c81 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -2685,6 +2685,34 @@ public class WifiManager { } /** + * Return the filtered ScanResults which may be authenticated by the suggested network + * configurations. + * @param networkSuggestions The list of {@link WifiNetworkSuggestion} + * @param scanResults The scan results to be filtered, this is optional, if it is null or + * empty, wifi system would use the recent scan results in the system. + * @return The map of {@link WifiNetworkSuggestion} and the list of {@link ScanResult} which + * may be authenticated by the corresponding network configuration. + * @hide + */ + @SystemApi + @RequiresPermission(allOf = {ACCESS_FINE_LOCATION, ACCESS_WIFI_STATE}) + @NonNull + public Map<WifiNetworkSuggestion, List<ScanResult>> getMatchingScanResults( + @NonNull List<WifiNetworkSuggestion> networkSuggestions, + @Nullable List<ScanResult> scanResults) { + if (networkSuggestions == null) { + throw new IllegalArgumentException("networkSuggestions must not be null."); + } + try { + return mService.getMatchingScanResults( + networkSuggestions, scanResults, + mContext.getOpPackageName(), mContext.getFeatureId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Check if scanning is always available. * * If this return {@code true}, apps can issue {@link #startScan} and fetch scan results @@ -4271,6 +4299,23 @@ public class WifiManager { } /** + * Allows the OEM to enable/disable auto-join globally. + * + * @param choice true to allow autojoin, false to disallow autojoin + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) + public void allowAutojoinGlobal(boolean choice) { + try { + mService.allowAutojoinGlobal(choice); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + + /** * Sets the user choice for allowing auto-join to a network. * The updated choice will be made available through the updated config supplied by the * CONFIGURED_NETWORKS_CHANGED broadcast. @@ -4897,18 +4942,6 @@ public class WifiManager { } /** - * Enable/disable WifiConnectivityManager - * @hide - */ - public void enableWifiConnectivityManager(boolean enabled) { - try { - mService.enableWifiConnectivityManager(enabled); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** * Returns a byte stream representing the data that needs to be backed up to save the * current Wifi state. * This Wifi state can be restored by calling {@link #restoreBackupData(byte[])}. diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java index 8fa9c3d6f1a6..9562f95ac162 100644 --- a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java +++ b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java @@ -66,12 +66,20 @@ public class WifiP2pConfig implements Parcelable { /** @hide */ public String passphrase = ""; - /** Get the required band for the group owner. */ + /** + * Get the required band for the group owner. + * The result will be one of the following: + * {@link #GROUP_OWNER_BAND_AUTO}, + * {@link #GROUP_OWNER_BAND_2GHZ}, + * {@link #GROUP_OWNER_BAND_5GHZ} + */ + @GroupOperatingBandType public int getGroupOwnerBand() { return groupOwnerBand; } /** @hide */ + @GroupOperatingBandType public int groupOwnerBand = GROUP_OWNER_BAND_AUTO; /** @hide */ diff --git a/wifi/java/com/android/server/wifi/BaseWifiService.java b/wifi/java/com/android/server/wifi/BaseWifiService.java index 56fa6e23a852..080c6c772f63 100644 --- a/wifi/java/com/android/server/wifi/BaseWifiService.java +++ b/wifi/java/com/android/server/wifi/BaseWifiService.java @@ -178,6 +178,11 @@ public class BaseWifiService extends IWifiManager.Stub { } @Override + public void allowAutojoinGlobal(boolean choice) { + throw new UnsupportedOperationException(); + } + + @Override public void allowAutojoin(int netId, boolean choice) { throw new UnsupportedOperationException(); } @@ -404,7 +409,8 @@ public class BaseWifiService extends IWifiManager.Stub { throw new UnsupportedOperationException(); } - @Override + /** @deprecated use {@link #allowAutojoinGlobal(boolean)} instead */ + @Deprecated public void enableWifiConnectivityManager(boolean enabled) { throw new UnsupportedOperationException(); } @@ -617,4 +623,12 @@ public class BaseWifiService extends IWifiManager.Stub { public void clearWifiConnectedNetworkScorer() { throw new UnsupportedOperationException(); } + + @Override + public Map<WifiNetworkSuggestion, List<ScanResult>> getMatchingScanResults( + List<WifiNetworkSuggestion> networkSuggestions, + List<ScanResult> scanResults, + String callingPackage, String callingFeatureId) { + throw new UnsupportedOperationException(); + } } diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java index 0ef75aa3eb5a..8023160a811e 100644 --- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java +++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java @@ -24,6 +24,7 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertTrue; import android.net.MacAddress; @@ -386,4 +387,43 @@ public class WifiConfigurationTest { .get(WifiConfiguration.GroupMgmtCipher.BIP_GMAC_256)); assertTrue(config.requirePMF); } + + /** + * Test that the NetworkSelectionStatus Builder returns the same values that was set, and that + * calling build multiple times returns different instances. + */ + @Test + public void testNetworkSelectionStatusBuilder() throws Exception { + NetworkSelectionStatus.Builder builder = new NetworkSelectionStatus.Builder() + .setNetworkSelectionDisableReason( + NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION) + .setNetworkSelectionStatus( + NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED); + + NetworkSelectionStatus status1 = builder.build(); + + assertEquals(NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION, + status1.getNetworkSelectionDisableReason()); + assertEquals(NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED, + status1.getNetworkSelectionStatus()); + + NetworkSelectionStatus status2 = builder + .setNetworkSelectionDisableReason(NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD) + .build(); + + // different instances + assertNotSame(status1, status2); + + // assert that status1 didn't change + assertEquals(NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION, + status1.getNetworkSelectionDisableReason()); + assertEquals(NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED, + status1.getNetworkSelectionStatus()); + + // assert that status2 changed + assertEquals(NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD, + status2.getNetworkSelectionDisableReason()); + assertEquals(NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED, + status2.getNetworkSelectionStatus()); + } } diff --git a/wifi/tests/src/android/net/wifi/WifiInfoTest.java b/wifi/tests/src/android/net/wifi/WifiInfoTest.java index af85ce05f23b..04759ac21bba 100644 --- a/wifi/tests/src/android/net/wifi/WifiInfoTest.java +++ b/wifi/tests/src/android/net/wifi/WifiInfoTest.java @@ -18,6 +18,7 @@ package android.net.wifi; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertTrue; import android.os.Parcel; @@ -26,6 +27,8 @@ import androidx.test.filters.SmallTest; import org.junit.Test; +import java.nio.charset.StandardCharsets; + /** * Unit tests for {@link android.net.wifi.WifiInfo}. */ @@ -41,6 +44,11 @@ public class WifiInfoTest { private static final int TEST_WIFI_STANDARD = ScanResult.WIFI_STANDARD_11AC; private static final int TEST_MAX_SUPPORTED_TX_LINK_SPEED_MBPS = 866; private static final int TEST_MAX_SUPPORTED_RX_LINK_SPEED_MBPS = 1200; + private static final String TEST_SSID = "Test123"; + private static final String TEST_BSSID = "12:12:12:12:12:12"; + private static final int TEST_RSSI = -60; + private static final int TEST_NETWORK_ID = 5; + private static final int TEST_NETWORK_ID2 = 6; /** * Verify parcel write/read with WifiInfo. @@ -101,4 +109,43 @@ public class WifiInfoTest { assertEquals(null, wifiInfo.getBSSID()); assertEquals(-1, wifiInfo.getNetworkId()); } + + /** + * Test that the WifiInfo Builder returns the same values that was set, and that + * calling build multiple times returns different instances. + */ + @Test + public void testWifiInfoBuilder() throws Exception { + WifiInfo.Builder builder = new WifiInfo.Builder() + .setSsid(TEST_SSID.getBytes(StandardCharsets.UTF_8)) + .setBssid(TEST_BSSID) + .setRssi(TEST_RSSI) + .setNetworkId(TEST_NETWORK_ID); + + WifiInfo info1 = builder.build(); + + assertEquals("\"" + TEST_SSID + "\"", info1.getSSID()); + assertEquals(TEST_BSSID, info1.getBSSID()); + assertEquals(TEST_RSSI, info1.getRssi()); + assertEquals(TEST_NETWORK_ID, info1.getNetworkId()); + + WifiInfo info2 = builder + .setNetworkId(TEST_NETWORK_ID2) + .build(); + + // different instances + assertNotSame(info1, info2); + + // assert that info1 didn't change + assertEquals("\"" + TEST_SSID + "\"", info1.getSSID()); + assertEquals(TEST_BSSID, info1.getBSSID()); + assertEquals(TEST_RSSI, info1.getRssi()); + assertEquals(TEST_NETWORK_ID, info1.getNetworkId()); + + // assert that info2 changed + assertEquals("\"" + TEST_SSID + "\"", info2.getSSID()); + assertEquals(TEST_BSSID, info2.getBSSID()); + assertEquals(TEST_RSSI, info2.getRssi()); + assertEquals(TEST_NETWORK_ID2, info2.getNetworkId()); + } } |