Merge "Refactor JNI to remove ITimeFilter/IDemux/ITuner"
diff --git a/Android.bp b/Android.bp
index 1a73e9d..ff62106 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1115,6 +1115,7 @@
"core/java/android/os/incremental/IStorageLoadingProgressListener.aidl",
"core/java/android/os/incremental/IncrementalNewFileParams.aidl",
"core/java/android/os/incremental/IStorageHealthListener.aidl",
+ "core/java/android/os/incremental/PerUidReadTimeouts.aidl",
"core/java/android/os/incremental/StorageHealthCheckParams.aidl",
],
path: "core/java",
diff --git a/MULTIUSER_OWNERS b/MULTIUSER_OWNERS
new file mode 100644
index 0000000..fbc611a
--- /dev/null
+++ b/MULTIUSER_OWNERS
@@ -0,0 +1,4 @@
+# OWNERS of Multiuser related files
+bookatz@google.com
+omakoto@google.com
+yamasani@google.com
diff --git a/apct-tests/perftests/multiuser/OWNERS b/apct-tests/perftests/multiuser/OWNERS
new file mode 100644
index 0000000..1a206cb
--- /dev/null
+++ b/apct-tests/perftests/multiuser/OWNERS
@@ -0,0 +1 @@
+include /MULTIUSER_OWNERS
\ No newline at end of file
diff --git a/apex/appsearch/framework/api/current.txt b/apex/appsearch/framework/api/current.txt
index ae9e7ff..ae32fba 100644
--- a/apex/appsearch/framework/api/current.txt
+++ b/apex/appsearch/framework/api/current.txt
@@ -100,6 +100,7 @@
method public long getCreationTimestampMillis();
method public static int getMaxIndexedProperties();
method @NonNull public String getNamespace();
+ method @Nullable public Object getProperty(@NonNull String);
method public boolean getPropertyBoolean(@NonNull String);
method @Nullable public boolean[] getPropertyBooleanArray(@NonNull String);
method @Nullable public byte[] getPropertyBytes(@NonNull String);
@@ -148,6 +149,12 @@
method @NonNull public android.app.appsearch.GetByUriRequest.Builder setNamespace(@NonNull String);
}
+ public class PackageIdentifier {
+ ctor public PackageIdentifier(@NonNull String, @NonNull byte[]);
+ method @NonNull public String getPackageName();
+ method @NonNull public byte[] getSha256Certificate();
+ }
+
public final class PutDocumentsRequest {
method @NonNull public java.util.List<android.app.appsearch.GenericDocument> getDocuments();
}
@@ -233,6 +240,8 @@
public final class SetSchemaRequest {
method @NonNull public java.util.Set<android.app.appsearch.AppSearchSchema> getSchemas();
+ method @NonNull public java.util.Set<java.lang.String> getSchemasNotVisibleToSystemUi();
+ method @NonNull public java.util.Map<java.lang.String,java.util.Set<android.app.appsearch.PackageIdentifier>> getSchemasVisibleToPackages();
method public boolean isForceOverride();
}
@@ -242,6 +251,17 @@
method @NonNull public android.app.appsearch.SetSchemaRequest.Builder addSchema(@NonNull java.util.Collection<android.app.appsearch.AppSearchSchema>);
method @NonNull public android.app.appsearch.SetSchemaRequest build();
method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setForceOverride(boolean);
+ method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setSchemaTypeVisibilityForPackage(@NonNull String, boolean, @NonNull android.app.appsearch.PackageIdentifier);
+ method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setSchemaTypeVisibilityForSystemUi(@NonNull String, boolean);
+ }
+
+}
+
+package android.app.appsearch.exceptions {
+
+ public class AppSearchException extends java.lang.Exception {
+ method public int getResultCode();
+ method @NonNull public <T> android.app.appsearch.AppSearchResult<T> toAppSearchResult();
}
}
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java b/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
index 85207f7..11e7fab 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
@@ -20,7 +20,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
-import android.app.appsearch.exceptions.AppSearchException;
import android.app.appsearch.util.BundleUtil;
import android.os.Bundle;
import android.util.Log;
@@ -92,9 +91,7 @@
/** Contains {@link GenericDocument} basic information (uri, schemaType etc). */
@NonNull final Bundle mBundle;
- /**
- * Contains all properties in {@link GenericDocument} to support getting properties via keys.
- */
+ /** Contains all properties in {@link GenericDocument} to support getting properties via keys */
@NonNull private final Bundle mProperties;
@NonNull private final String mUri;
@@ -202,6 +199,24 @@
}
/**
+ * Retrieves the property value with the given key as {@link Object}.
+ *
+ * @param key The key to look for.
+ * @return The entry with the given key as an object or {@code null} if there is no such key.
+ */
+ @Nullable
+ public Object getProperty(@NonNull String key) {
+ Preconditions.checkNotNull(key);
+ Object property = mProperties.get(key);
+ if (property instanceof ArrayList) {
+ return getPropertyBytesArray(key);
+ } else if (property instanceof Bundle[]) {
+ return getPropertyDocumentArray(key);
+ }
+ return property;
+ }
+
+ /**
* Retrieves a {@link String} value by key.
*
* @param key The key to look for.
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/PackageIdentifier.java b/apex/appsearch/framework/java/external/android/app/appsearch/PackageIdentifier.java
index 8b20c09..43be442 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/PackageIdentifier.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/PackageIdentifier.java
@@ -23,10 +23,7 @@
import java.util.Arrays;
import java.util.Objects;
-/**
- * This class represents a uniquely identifiable package.
- * @hide
- */
+/** This class represents a uniquely identifiable package. */
public class PackageIdentifier {
private final String mPackageName;
private final byte[] mSha256Certificate;
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/PutDocumentsRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/PutDocumentsRequest.java
index 1c360a6..b9503ee 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/PutDocumentsRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/PutDocumentsRequest.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.annotation.SuppressLint;
-import android.app.appsearch.exceptions.AppSearchException;
import com.android.internal.util.Preconditions;
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java b/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java
index 5ffa7c9..eb0b7324 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java
@@ -49,31 +49,20 @@
/** @hide */
public static final String MATCHES_FIELD = "matches";
- @NonNull private final Bundle mBundle;
+ /** @hide */
+ public static final String PACKAGE_NAME_FIELD = "packageName";
- @NonNull private final Bundle mDocumentBundle;
+ @NonNull private final Bundle mBundle;
/** Cache of the inflated document. Comes from inflating mDocumentBundle at first use. */
@Nullable private GenericDocument mDocument;
- /**
- * Contains a list of MatchInfo bundles that matched the request.
- *
- * <p>Only populated when requested in both {@link SearchSpec.Builder#setSnippetCount} and
- * {@link SearchSpec.Builder#setSnippetCountPerProperty}.
- *
- * @see #getMatches()
- */
- @NonNull private final List<Bundle> mMatchBundles;
-
/** Cache of the inflated matches. Comes from inflating mMatchBundles at first use. */
@Nullable private List<MatchInfo> mMatches;
/** @hide */
public SearchResult(@NonNull Bundle bundle) {
mBundle = Preconditions.checkNotNull(bundle);
- mDocumentBundle = Preconditions.checkNotNull(bundle.getBundle(DOCUMENT_FIELD));
- mMatchBundles = Preconditions.checkNotNull(bundle.getParcelableArrayList(MATCHES_FIELD));
}
/** @hide */
@@ -90,7 +79,9 @@
@NonNull
public GenericDocument getDocument() {
if (mDocument == null) {
- mDocument = new GenericDocument(mDocumentBundle);
+ mDocument =
+ new GenericDocument(
+ Preconditions.checkNotNull(mBundle.getBundle(DOCUMENT_FIELD)));
}
return mDocument;
}
@@ -106,9 +97,11 @@
@NonNull
public List<MatchInfo> getMatches() {
if (mMatches == null) {
- mMatches = new ArrayList<>(mMatchBundles.size());
- for (int i = 0; i < mMatchBundles.size(); i++) {
- MatchInfo matchInfo = new MatchInfo(getDocument(), mMatchBundles.get(i));
+ List<Bundle> matchBundles =
+ Preconditions.checkNotNull(mBundle.getParcelableArrayList(MATCHES_FIELD));
+ mMatches = new ArrayList<>(matchBundles.size());
+ for (int i = 0; i < matchBundles.size(); i++) {
+ MatchInfo matchInfo = new MatchInfo(getDocument(), matchBundles.get(i));
mMatches.add(matchInfo);
}
}
@@ -116,6 +109,17 @@
}
/**
+ * Contains the package name that stored the {@link GenericDocument}.
+ *
+ * @return Package name that stored the document
+ * @hide
+ */
+ @NonNull
+ public String getPackageName() {
+ return Preconditions.checkNotNull(mBundle.getString(PACKAGE_NAME_FIELD));
+ }
+
+ /**
* 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.
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java b/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java
index 400b630..c3f0d8a 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java
@@ -19,8 +19,6 @@
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
-import android.annotation.SuppressLint;
-import android.app.appsearch.exceptions.AppSearchException;
import android.app.appsearch.exceptions.IllegalSearchSpecException;
import android.os.Bundle;
import android.util.ArrayMap;
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java
index ad3ee05..e9c4cb4 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java
@@ -59,7 +59,6 @@
/**
* Returns the set of schema types that have opted out of being visible on system UI surfaces.
- * @hide
*/
@NonNull
public Set<String> getSchemasNotVisibleToSystemUi() {
@@ -72,7 +71,6 @@
* certificate.
*
* <p>This method is inefficient to call repeatedly.
- * @hide
*/
@NonNull
public Map<String, Set<PackageIdentifier>> getSchemasVisibleToPackages() {
@@ -141,7 +139,6 @@
*
* @param schemaType The schema type to set visibility on.
* @param visible Whether the {@code schemaType} will be visible or not.
- * @hide
*/
// Merged list available from getSchemasNotVisibleToSystemUi
@SuppressLint("MissingGetterMatchingBuilder")
@@ -165,7 +162,6 @@
* @param schemaType The schema type to set visibility on.
* @param visible Whether the {@code schemaType} will be visible or not.
* @param packageIdentifier Represents the package that will be granted visibility.
- * @hide
*/
// Merged list available from getSchemasVisibleToPackages
@SuppressLint("MissingGetterMatchingBuilder")
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/exceptions/AppSearchException.java b/apex/appsearch/framework/java/external/android/app/appsearch/exceptions/AppSearchException.java
index 704f180..b1a33a4 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/exceptions/AppSearchException.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/exceptions/AppSearchException.java
@@ -25,8 +25,6 @@
*
* <p>These exceptions can be converted into a failed {@link AppSearchResult} for propagating to the
* client.
- *
- * @hide
*/
public class AppSearchException extends Exception {
private final @AppSearchResult.ResultCode int mResultCode;
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
index 47a81eb..a2126b1 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
@@ -37,6 +37,7 @@
import com.android.server.appsearch.external.localstorage.converter.SearchSpecToProtoConverter;
import com.google.android.icing.IcingSearchEngine;
+import com.google.android.icing.proto.DeleteByQueryResultProto;
import com.google.android.icing.proto.DeleteResultProto;
import com.google.android.icing.proto.DocumentProto;
import com.google.android.icing.proto.GetAllNamespacesResultProto;
@@ -609,7 +610,7 @@
SearchSpecProto searchSpecProto = SearchSpecToProtoConverter.toSearchSpecProto(searchSpec);
SearchSpecProto.Builder searchSpecBuilder =
searchSpecProto.toBuilder().setQuery(queryExpression);
- DeleteResultProto deleteResultProto;
+ DeleteByQueryResultProto deleteResultProto;
mReadWriteLock.writeLock().lock();
try {
// Only rewrite SearchSpec for non empty prefixes.
@@ -797,11 +798,27 @@
* Removes any prefixes from types and namespaces mentioned anywhere in {@code documentBuilder}.
*
* @param documentBuilder The document to mutate
+ * @return Prefix name that was removed from the document.
+ * @throws AppSearchException if there are unexpected database prefixing errors.
*/
+ @NonNull
@VisibleForTesting
- static void removePrefixesFromDocument(@NonNull DocumentProto.Builder documentBuilder)
+ static String removePrefixesFromDocument(@NonNull DocumentProto.Builder documentBuilder)
throws AppSearchException {
// Rewrite the type name and namespace to remove the prefix.
+ String schemaPrefix = getPrefix(documentBuilder.getSchema());
+ String namespacePrefix = getPrefix(documentBuilder.getNamespace());
+
+ if (!schemaPrefix.equals(namespacePrefix)) {
+ throw new AppSearchException(
+ AppSearchResult.RESULT_INTERNAL_ERROR,
+ "Found unexpected"
+ + " multiple prefix names in document: "
+ + schemaPrefix
+ + ", "
+ + namespacePrefix);
+ }
+
documentBuilder.setSchema(removePrefix(documentBuilder.getSchema()));
documentBuilder.setNamespace(removePrefix(documentBuilder.getNamespace()));
@@ -816,12 +833,22 @@
for (int documentIdx = 0; documentIdx < documentCount; documentIdx++) {
DocumentProto.Builder derivedDocumentBuilder =
propertyBuilder.getDocumentValues(documentIdx).toBuilder();
- removePrefixesFromDocument(derivedDocumentBuilder);
+ String nestedPrefix = removePrefixesFromDocument(derivedDocumentBuilder);
+ if (!nestedPrefix.equals(schemaPrefix)) {
+ throw new AppSearchException(
+ AppSearchResult.RESULT_INTERNAL_ERROR,
+ "Found unexpected multiple prefix names in document: "
+ + schemaPrefix
+ + ", "
+ + nestedPrefix);
+ }
propertyBuilder.setDocumentValues(documentIdx, derivedDocumentBuilder);
}
documentBuilder.setProperties(propertyIdx, propertyBuilder);
}
}
+
+ return schemaPrefix;
}
/**
@@ -929,6 +956,25 @@
return packageName + PACKAGE_DELIMITER + databaseName + DATABASE_DELIMITER;
}
+ /**
+ * Returns the package name that's contained within the {@code prefix}.
+ *
+ * @param prefix Prefix string that contains the package name inside of it. The package name
+ * must be in the front of the string, and separated from the rest of the string by the
+ * {@link #PACKAGE_DELIMITER}.
+ * @return Valid package name.
+ */
+ @NonNull
+ private static String getPackageName(@NonNull String prefix) {
+ int delimiterIndex = prefix.indexOf(PACKAGE_DELIMITER);
+ if (delimiterIndex == -1) {
+ // This should never happen if we construct our prefixes properly
+ Log.wtf(TAG, "Malformed prefix doesn't contain package name: " + prefix);
+ return "";
+ }
+ return prefix.substring(0, delimiterIndex);
+ }
+
@NonNull
private static String removePrefix(@NonNull String prefixedString) throws AppSearchException {
// The prefix is made up of the package, then the database. So we only need to find the
@@ -949,7 +995,7 @@
if (databaseDelimiterIndex == -1) {
throw new AppSearchException(
AppSearchResult.RESULT_UNKNOWN_ERROR,
- "The databaseName prefixed value doesn't contains a valid database name.");
+ "The databaseName prefixed value doesn't contain a valid database name.");
}
// Add 1 to include the char size of the DATABASE_DELIMITER
@@ -1034,20 +1080,24 @@
}
/** Remove the rewritten schema types from any result documents. */
- private static SearchResultPage rewriteSearchResultProto(
- @NonNull SearchResultProto searchResultProto) throws AppSearchException {
+ @NonNull
+ @VisibleForTesting
+ static SearchResultPage rewriteSearchResultProto(@NonNull SearchResultProto searchResultProto)
+ throws AppSearchException {
+ // Parallel array of package names for each document search result.
+ List<String> packageNames = new ArrayList<>(searchResultProto.getResultsCount());
+
SearchResultProto.Builder resultsBuilder = searchResultProto.toBuilder();
for (int i = 0; i < searchResultProto.getResultsCount(); i++) {
- if (searchResultProto.getResults(i).hasDocument()) {
- SearchResultProto.ResultProto.Builder resultBuilder =
- searchResultProto.getResults(i).toBuilder();
- DocumentProto.Builder documentBuilder = resultBuilder.getDocument().toBuilder();
- removePrefixesFromDocument(documentBuilder);
- resultBuilder.setDocument(documentBuilder);
- resultsBuilder.setResults(i, resultBuilder);
- }
+ SearchResultProto.ResultProto.Builder resultBuilder =
+ searchResultProto.getResults(i).toBuilder();
+ DocumentProto.Builder documentBuilder = resultBuilder.getDocument().toBuilder();
+ String prefix = removePrefixesFromDocument(documentBuilder);
+ packageNames.add(getPackageName(prefix));
+ resultBuilder.setDocument(documentBuilder);
+ resultsBuilder.setResults(i, resultBuilder);
}
- return SearchResultToProtoConverter.toSearchResultPage(resultsBuilder);
+ return SearchResultToProtoConverter.toSearchResultPage(resultsBuilder, packageNames);
}
@GuardedBy("mReadWriteLock")
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverter.java
index 5474cd0..a2386ec 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverter.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverter.java
@@ -54,40 +54,43 @@
for (int i = 0; i < keys.size(); i++) {
String name = keys.get(i);
PropertyProto.Builder propertyProto = PropertyProto.newBuilder().setName(name);
- String[] stringValues = document.getPropertyStringArray(name);
- long[] longValues = document.getPropertyLongArray(name);
- double[] doubleValues = document.getPropertyDoubleArray(name);
- boolean[] booleanValues = document.getPropertyBooleanArray(name);
- byte[][] bytesValues = document.getPropertyBytesArray(name);
- GenericDocument[] documentValues = document.getPropertyDocumentArray(name);
- if (stringValues != null) {
+ Object property = document.getProperty(name);
+ if (property instanceof String[]) {
+ String[] stringValues = (String[]) property;
for (int j = 0; j < stringValues.length; j++) {
propertyProto.addStringValues(stringValues[j]);
}
- } else if (longValues != null) {
+ } else if (property instanceof long[]) {
+ long[] longValues = (long[]) property;
for (int j = 0; j < longValues.length; j++) {
propertyProto.addInt64Values(longValues[j]);
}
- } else if (doubleValues != null) {
+ } else if (property instanceof double[]) {
+ double[] doubleValues = (double[]) property;
for (int j = 0; j < doubleValues.length; j++) {
propertyProto.addDoubleValues(doubleValues[j]);
}
- } else if (booleanValues != null) {
+ } else if (property instanceof boolean[]) {
+ boolean[] booleanValues = (boolean[]) property;
for (int j = 0; j < booleanValues.length; j++) {
propertyProto.addBooleanValues(booleanValues[j]);
}
- } else if (bytesValues != null) {
+ } else if (property instanceof byte[][]) {
+ byte[][] bytesValues = (byte[][]) property;
for (int j = 0; j < bytesValues.length; j++) {
propertyProto.addBytesValues(ByteString.copyFrom(bytesValues[j]));
}
- } else if (documentValues != null) {
+ } else if (property instanceof GenericDocument[]) {
+ GenericDocument[] documentValues = (GenericDocument[]) property;
for (int j = 0; j < documentValues.length; j++) {
DocumentProto proto = toDocumentProto(documentValues[j]);
propertyProto.addDocumentValues(proto);
}
} else {
throw new IllegalStateException(
- "Property \"" + name + "\" has unsupported value type");
+ String.format(
+ "Property \"%s\" has unsupported value type %s",
+ name, property.getClass().toString()));
}
mProtoBuilder.addProperties(propertyProto);
}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchResultToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchResultToProtoConverter.java
index 4d107a9..ccd567d 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchResultToProtoConverter.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchResultToProtoConverter.java
@@ -22,12 +22,15 @@
import android.app.appsearch.SearchResultPage;
import android.os.Bundle;
+import com.android.internal.util.Preconditions;
+
import com.google.android.icing.proto.SearchResultProto;
import com.google.android.icing.proto.SearchResultProtoOrBuilder;
import com.google.android.icing.proto.SnippetMatchProto;
import com.google.android.icing.proto.SnippetProto;
import java.util.ArrayList;
+import java.util.List;
/**
* Translates a {@link SearchResultProto} into {@link SearchResult}s.
@@ -37,27 +40,45 @@
public class SearchResultToProtoConverter {
private SearchResultToProtoConverter() {}
- /** Translate a {@link SearchResultProto} into {@link SearchResultPage}. */
+ /**
+ * Translate a {@link SearchResultProto} into {@link SearchResultPage}.
+ *
+ * @param proto The {@link SearchResultProto} containing results.
+ * @param packageNames A parallel array of package names. The package name at index 'i' of this
+ * list should be the package that indexed the document at index 'i' of proto.getResults(i).
+ * @return {@link SearchResultPage} of results.
+ */
@NonNull
- public static SearchResultPage toSearchResultPage(@NonNull SearchResultProtoOrBuilder proto) {
+ public static SearchResultPage toSearchResultPage(
+ @NonNull SearchResultProtoOrBuilder proto, @NonNull List<String> packageNames) {
+ Preconditions.checkArgument(
+ proto.getResultsCount() == packageNames.size(),
+ "Size of " + "results does not match the number of package names.");
Bundle bundle = new Bundle();
bundle.putLong(SearchResultPage.NEXT_PAGE_TOKEN_FIELD, proto.getNextPageToken());
ArrayList<Bundle> resultBundles = new ArrayList<>(proto.getResultsCount());
for (int i = 0; i < proto.getResultsCount(); i++) {
- resultBundles.add(toSearchResultBundle(proto.getResults(i)));
+ resultBundles.add(toSearchResultBundle(proto.getResults(i), packageNames.get(i)));
}
bundle.putParcelableArrayList(SearchResultPage.RESULTS_FIELD, resultBundles);
return new SearchResultPage(bundle);
}
- /** Translate a {@link SearchResultProto.ResultProto} into {@link SearchResult}. */
+ /**
+ * Translate a {@link SearchResultProto.ResultProto} into {@link SearchResult}.
+ *
+ * @param proto The proto to be converted.
+ * @param packageName The package name associated with the document in {@code proto}.
+ * @return A {@link SearchResult} bundle.
+ */
@NonNull
private static Bundle toSearchResultBundle(
- @NonNull SearchResultProto.ResultProtoOrBuilder proto) {
+ @NonNull SearchResultProto.ResultProtoOrBuilder proto, @NonNull String packageName) {
Bundle bundle = new Bundle();
GenericDocument document =
GenericDocumentToProtoConverter.toGenericDocument(proto.getDocument());
bundle.putBundle(SearchResult.DOCUMENT_FIELD, document.getBundle());
+ bundle.putString(SearchResult.PACKAGE_NAME_FIELD, packageName);
ArrayList<Bundle> matchList = new ArrayList<>();
if (proto.hasSnippet()) {
diff --git a/apex/appsearch/synced_jetpack_changeid.txt b/apex/appsearch/synced_jetpack_changeid.txt
index 2b1ec08..73f64dc 100644
--- a/apex/appsearch/synced_jetpack_changeid.txt
+++ b/apex/appsearch/synced_jetpack_changeid.txt
@@ -1 +1 @@
-I596ad1269b4d3a4f26db67f5d970aeaa3bf94a9d
+I8b7425b3f87153547d1c8f5b560be5a54c9be97e
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java
index 9e22bf6..6859747 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java
@@ -46,7 +46,7 @@
private final ExecutorService mExecutor;
@NonNull
- public static ListenableFuture<GlobalSearchSessionShimImpl> createGlobalSearchSession() {
+ public static ListenableFuture<GlobalSearchSessionShim> createGlobalSearchSession() {
Context context = ApplicationProvider.getApplicationContext();
AppSearchManager appSearchManager = context.getSystemService(AppSearchManager.class);
SettableFuture<AppSearchResult<GlobalSearchSession>> future = SettableFuture.create();
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java
index 8383df4..e439c5a 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package android.app.appsearch;
import android.annotation.NonNull;
@@ -26,7 +27,7 @@
* Represents a connection to an AppSearch storage system where {@link GenericDocument}s can be
* placed and queried.
*
- * All implementations of this interface must be thread safe.
+ * <p>All implementations of this interface must be thread safe.
*/
public interface AppSearchSessionShim {
@@ -37,41 +38,42 @@
* to {@link #setSchema}, if any, to determine how to treat existing documents. The following
* types of schema modifications are always safe and are made without deleting any existing
* documents:
+ *
* <ul>
- * <li>Addition of new types
- * <li>Addition of new
- * {@link AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} or
- * {@link AppSearchSchema.PropertyConfig#CARDINALITY_REPEATED REPEATED} properties to a
- * type
- * <li>Changing the cardinality of a data type to be less restrictive (e.g. changing an
- * {@link AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} property into a
- * {@link AppSearchSchema.PropertyConfig#CARDINALITY_REPEATED REPEATED} property.
+ * <li>Addition of new types
+ * <li>Addition of new {@link AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} or
+ * {@link AppSearchSchema.PropertyConfig#CARDINALITY_REPEATED REPEATED} properties to a
+ * type
+ * <li>Changing the cardinality of a data type to be less restrictive (e.g. changing an {@link
+ * AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} property into a {@link
+ * AppSearchSchema.PropertyConfig#CARDINALITY_REPEATED REPEATED} property.
* </ul>
*
* <p>The following types of schema changes are not backwards-compatible:
+ *
* <ul>
- * <li>Removal of an existing type
- * <li>Removal of a property from a type
- * <li>Changing the data type ({@code boolean}, {@code long}, etc.) of an existing property
- * <li>For properties of {@code Document} type, changing the schema type of
- * {@code Document}s of that property
- * <li>Changing the cardinality of a data type to be more restrictive (e.g. changing an
- * {@link AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} property into a
- * {@link AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED REQUIRED} property).
- * <li>Adding a
- * {@link AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED REQUIRED} property.
+ * <li>Removal of an existing type
+ * <li>Removal of a property from a type
+ * <li>Changing the data type ({@code boolean}, {@code long}, etc.) of an existing property
+ * <li>For properties of {@code Document} type, changing the schema type of {@code Document}s
+ * of that property
+ * <li>Changing the cardinality of a data type to be more restrictive (e.g. changing an {@link
+ * AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} property into a {@link
+ * AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED REQUIRED} property).
+ * <li>Adding a {@link AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED REQUIRED} property.
* </ul>
+ *
* <p>Supplying a schema with such changes will, by default, result in this call completing its
- * future with an {@link androidx.appsearch.exceptions.AppSearchException} with a code of
+ * future with an {@link android.app.appsearch.exceptions.AppSearchException} with a code of
* {@link AppSearchResult#RESULT_INVALID_SCHEMA} and a message describing the incompatibility.
* In this case the previously set schema will remain active.
*
* <p>If you need to make non-backwards-compatible changes as described above, you can set the
* {@link SetSchemaRequest.Builder#setForceOverride} method to {@code true}. In this case,
- * instead of completing its future with an
- * {@link androidx.appsearch.exceptions.AppSearchException} with the
- * {@link AppSearchResult#RESULT_INVALID_SCHEMA} error code, all documents which are not
- * compatible with the new schema will be deleted and the incompatible schema will be applied.
+ * instead of completing its future with an {@link
+ * android.app.appsearch.exceptions.AppSearchException} with the {@link
+ * AppSearchResult#RESULT_INVALID_SCHEMA} error code, all documents which are not compatible
+ * with the new schema will be deleted and the incompatible schema will be applied.
*
* <p>It is a no-op to set the same schema as has been previously set; this is handled
* efficiently.
@@ -79,8 +81,8 @@
* <p>By default, documents are visible on platform surfaces. To opt out, call {@code
* SetSchemaRequest.Builder#setPlatformSurfaceable} with {@code surfaceable} as false. Any
* visibility settings apply only to the schemas that are included in the {@code request}.
- * Visibility settings for a schema type do not apply or persist across
- * {@link SetSchemaRequest}s.
+ * Visibility settings for a schema type do not apply or persist across {@link
+ * SetSchemaRequest}s.
*
* @param request The schema update request.
* @return The pending result of performing this operation.
@@ -107,10 +109,9 @@
* schema type previously registered via the {@link #setSchema} method.
*
* @param request {@link PutDocumentsRequest} containing documents to be indexed
- * @return The pending result of performing this operation. The keys of the returned
- * {@link AppSearchBatchResult} are the URIs of the input documents. The values are
- * {@code null} if they were successfully indexed, or a failed {@link AppSearchResult}
- * otherwise.
+ * @return The pending result of performing this operation. The keys of the returned {@link
+ * AppSearchBatchResult} are the URIs of the input documents. The values are {@code null} if
+ * they were successfully indexed, or a failed {@link AppSearchResult} otherwise.
*/
@NonNull
ListenableFuture<AppSearchBatchResult<String, Void>> putDocuments(
@@ -120,11 +121,11 @@
* Retrieves {@link GenericDocument}s by URI.
*
* @param request {@link GetByUriRequest} containing URIs to be retrieved.
- * @return The pending result of performing this operation. The keys of the returned
- * {@link AppSearchBatchResult} are the input URIs. The values are the returned
- * {@link GenericDocument}s on success, or a failed {@link AppSearchResult} otherwise.
- * URIs that are not found will return a failed {@link AppSearchResult} with a result code
- * of {@link AppSearchResult#RESULT_NOT_FOUND}.
+ * @return The pending result of performing this operation. The keys of the returned {@link
+ * AppSearchBatchResult} are the input URIs. The values are the returned {@link
+ * GenericDocument}s on success, or a failed {@link AppSearchResult} otherwise. URIs that
+ * are not found will return a failed {@link AppSearchResult} with a result code of {@link
+ * AppSearchResult#RESULT_NOT_FOUND}.
*/
@NonNull
ListenableFuture<AppSearchBatchResult<String, GenericDocument>> getByUri(
@@ -134,42 +135,39 @@
* Searches a document based on a given query string.
*
* <p>Currently we support following features in the raw query format:
+ *
* <ul>
- * <li>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
- * <p>OR joins (e.g. “match documents that have either the term ‘dog’ or
- * ‘cat’”).
- * Example: dog OR puppy
- * <li>Exclusion
- * <p>Exclude a term (e.g. “match documents that do
- * not have the term ‘dog’”).
- * Example: -dog excludes the term ‘dog’
- * <li>Grouping terms
- * <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
- * <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
- * <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
- * that contain the query term ‘dog’ and are of either the ‘Email’ schema type or the
- * ‘Video’ schema type.
+ * <li>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
+ * <p>OR joins (e.g. “match documents that have either the term ‘dog’ or ‘cat’”). Example:
+ * dog OR puppy
+ * <li>Exclusion
+ * <p>Exclude a term (e.g. “match documents that do not have the term ‘dog’”). Example:
+ * -dog excludes the term ‘dog’
+ * <li>Grouping terms
+ * <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
+ * <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
+ * <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 that contain the query term ‘dog’ and are of either the ‘Email’ schema
+ * type or the ‘Video’ schema type.
* </ul>
*
- * <p> This method is lightweight. The heavy work will be done in
- * {@link SearchResults#getNextPage()}.
+ * <p>This method is lightweight. The heavy work will be done in {@link
+ * SearchResultsShim#getNextPage()}.
*
* @param queryExpression Query String to search.
- * @param searchSpec Spec for setting filters, raw query etc.
+ * @param searchSpec Spec for setting filters, raw query etc.
* @return The search result of performing this operation.
*/
@NonNull
@@ -179,11 +177,10 @@
* Removes {@link GenericDocument}s from the index by URI.
*
* @param request Request containing URIs to be removed.
- * @return The pending result of performing this operation. The keys of the returned
- * {@link AppSearchBatchResult} are the input URIs. The values are {@code null} on success,
- * or a failed {@link AppSearchResult} otherwise. URIs that are not found will return a
- * failed {@link AppSearchResult} with a result code of
- * {@link AppSearchResult#RESULT_NOT_FOUND}.
+ * @return The pending result of performing this operation. The keys of the returned {@link
+ * AppSearchBatchResult} are the input URIs. The values are {@code null} on success, or a
+ * failed {@link AppSearchResult} otherwise. URIs that are not found will return a failed
+ * {@link AppSearchResult} with a result code of {@link AppSearchResult#RESULT_NOT_FOUND}.
*/
@NonNull
ListenableFuture<AppSearchBatchResult<String, Void>> removeByUri(
@@ -191,18 +188,18 @@
/**
* Removes {@link GenericDocument}s from the index by Query. Documents will be removed if they
- * match the {@code queryExpression} in given namespaces and schemaTypes which is set via
- * {@link SearchSpec.Builder#addNamespace} and {@link SearchSpec.Builder#addSchemaType}.
+ * match the {@code queryExpression} in given namespaces and schemaTypes which is set via {@link
+ * SearchSpec.Builder#addNamespace} and {@link SearchSpec.Builder#addSchemaType}.
*
- * <p> An empty {@code queryExpression} matches all documents.
+ * <p>An empty {@code queryExpression} matches all documents.
*
- * <p> An empty set of namespaces or schemaTypes matches all namespaces or schemaTypes in
- * the current database.
+ * <p>An empty set of namespaces or schemaTypes matches all namespaces or schemaTypes in the
+ * current database.
*
* @param queryExpression Query String to search.
- * @param searchSpec Spec containing schemaTypes, namespaces and query expression
- * indicates how document will be removed. All specific about how to
- * scoring, ordering, snippeting and resulting will be ignored.
+ * @param searchSpec Spec containing schemaTypes, namespaces and query expression indicates how
+ * document will be removed. All specific about how to scoring, ordering, snippeting and
+ * resulting will be ignored.
* @return The pending result of performing this operation.
*/
@NonNull
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java
index 33dc379..2d09247 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package android.app.appsearch;
import android.annotation.NonNull;
@@ -27,42 +28,39 @@
* Searches across all documents in the storage based on a given query string.
*
* <p>Currently we support following features in the raw query format:
+ *
* <ul>
- * <li>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
- * <p>OR joins (e.g. “match documents that have either the term ‘dog’ or
- * ‘cat’”).
- * Example: dog OR puppy
- * <li>Exclusion
- * <p>Exclude a term (e.g. “match documents that do
- * not have the term ‘dog’”).
- * Example: -dog excludes the term ‘dog’
- * <li>Grouping terms
- * <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
- * <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
- * <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
- * that contain the query term ‘dog’ and are of either the ‘Email’ schema type or the
- * ‘Video’ schema type.
+ * <li>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
+ * <p>OR joins (e.g. “match documents that have either the term ‘dog’ or ‘cat’”). Example:
+ * dog OR puppy
+ * <li>Exclusion
+ * <p>Exclude a term (e.g. “match documents that do not have the term ‘dog’”). Example:
+ * -dog excludes the term ‘dog’
+ * <li>Grouping terms
+ * <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
+ * <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
+ * <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 that contain the query term ‘dog’ and are of either the ‘Email’ schema
+ * type or the ‘Video’ schema type.
* </ul>
*
- * <p> This method is lightweight. The heavy work will be done in
- * {@link SearchResults#getNextPage}.
+ * <p>This method is lightweight. The heavy work will be done in {@link
+ * SearchResultsShim#getNextPage()}.
*
* @param queryExpression Query String to search.
- * @param searchSpec Spec for setting filters, raw query etc.
+ * @param searchSpec Spec for setting filters, raw query etc.
* @return The search result of performing this operation.
*/
@NonNull
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/SearchResultsShim.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/SearchResultsShim.java
index f387a17..328c65c 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/SearchResultsShim.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/SearchResultsShim.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package android.app.appsearch;
import android.annotation.NonNull;
@@ -23,10 +24,10 @@
import java.util.List;
/**
- * SearchResults are a returned object from a query API.
+ * SearchResultsShim are a returned object from a query API.
*
- * <p>Each {@link SearchResult} contains a document and may contain other fields like snippets
- * based on request.
+ * <p>Each {@link SearchResult} contains a document and may contain other fields like snippets based
+ * on request.
*
* <p>Should close this object after finish fetching results.
*
@@ -36,11 +37,10 @@
/**
* Gets a whole page of {@link SearchResult}s.
*
- * <p>Re-call this method to get next page of {@link SearchResult}, until it returns an
- * empty list.
+ * <p>Re-call this method to get next page of {@link SearchResult}, until it returns an empty
+ * list.
*
- * <p>The page size is set by
- * {@link android.app.appsearch.SearchSpec.Builder#setResultCountPerPage}.
+ * <p>The page size is set by {@link SearchSpec.Builder#setResultCountPerPage}.
*
* @return The pending result of performing this operation.
*/
diff --git a/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl b/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl
index 7d02d2d..5693abe 100644
--- a/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl
+++ b/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl
@@ -41,7 +41,8 @@
int[] getAppIdTempWhitelist();
boolean isPowerSaveWhitelistExceptIdleApp(String name);
boolean isPowerSaveWhitelistApp(String name);
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = 30,
+ publicAlternatives = "Use SystemApi {@code PowerWhitelistManager#whitelistAppTemporarily(String, int, String)}.")
void addPowerSaveTempWhitelistApp(String name, long duration, int userId, String reason);
long addPowerSaveTempWhitelistAppForMms(String name, int userId, String reason);
long addPowerSaveTempWhitelistAppForSms(String name, int userId, String reason);
diff --git a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
index 4dc9cf8..cc3e9c3 100644
--- a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
+++ b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
@@ -274,11 +274,8 @@
int uid, @NonNull String packageName) {
updateJobsForUidPackage(uid, packageName, sender.isUidActive(uid));
- if (!sender.areAlarmsRestricted(uid, packageName, /*allowWhileIdle=*/ false)) {
+ if (!sender.areAlarmsRestricted(uid, packageName)) {
unblockAlarmsForUidPackage(uid, packageName);
- } else if (!sender.areAlarmsRestricted(uid, packageName, /*allowWhileIdle=*/ true)) {
- // we need to deliver the allow-while-idle alarms for this uid, package
- unblockAllUnrestrictedAlarms();
}
if (!sender.isRunAnyInBackgroundAppOpsAllowed(uid, packageName)) {
@@ -302,6 +299,7 @@
final boolean isActive = sender.isUidActive(uid);
updateJobsForUid(uid, isActive);
+ updateAlarmsForUid(uid);
if (isActive) {
unblockAlarmsForUid(uid);
@@ -313,7 +311,7 @@
*/
private void onPowerSaveUnexempted(AppStateTrackerImpl sender) {
updateAllJobs();
- unblockAllUnrestrictedAlarms();
+ updateAllAlarms();
}
/**
@@ -322,6 +320,8 @@
*/
private void onPowerSaveExemptionListChanged(AppStateTrackerImpl sender) {
updateAllJobs();
+ updateAllAlarms();
+ unblockAllUnrestrictedAlarms();
}
/**
@@ -344,7 +344,7 @@
private void onExemptedBucketChanged(AppStateTrackerImpl sender) {
// This doesn't happen very often, so just re-evaluate all jobs / alarms.
updateAllJobs();
- unblockAllUnrestrictedAlarms();
+ updateAllAlarms();
}
/**
@@ -352,10 +352,7 @@
*/
private void onForceAllAppsStandbyChanged(AppStateTrackerImpl sender) {
updateAllJobs();
-
- if (!sender.isForceAllAppsStandbyEnabled()) {
- unblockAllUnrestrictedAlarms();
- }
+ updateAllAlarms();
}
/**
@@ -387,6 +384,19 @@
}
/**
+ * Called when all alarms need to be re-evaluated for eligibility based on
+ * {@link #areAlarmsRestrictedByBatterySaver}.
+ */
+ public void updateAllAlarms() {
+ }
+
+ /**
+ * Called when the given uid state changes to active / idle.
+ */
+ public void updateAlarmsForUid(int uid) {
+ }
+
+ /**
* Called when the job restrictions for multiple UIDs might have changed, so the alarm
* manager should re-evaluate all restrictions for all blocked jobs.
*/
@@ -918,7 +928,7 @@
// Feature flag for forced app standby changed.
final boolean unblockAlarms;
synchronized (mLock) {
- unblockAlarms = !mForcedAppStandbyEnabled && !mForceAllAppsStandby;
+ unblockAlarms = !mForcedAppStandbyEnabled;
}
for (Listener l : cloneListeners()) {
l.updateAllJobs();
@@ -1109,38 +1119,11 @@
}
/**
- * @return whether alarms should be restricted for a UID package-name.
+ * @return whether alarms should be restricted for a UID package-name, due to explicit
+ * user-forced app standby. Use {{@link #areAlarmsRestrictedByBatterySaver} to check for
+ * restrictions induced by battery saver.
*/
- public boolean areAlarmsRestricted(int uid, @NonNull String packageName,
- boolean isExemptOnBatterySaver) {
- return isRestricted(uid, packageName, /*useTempExemptionListToo=*/ false,
- isExemptOnBatterySaver);
- }
-
- /**
- * @return whether jobs should be restricted for a UID package-name.
- */
- public boolean areJobsRestricted(int uid, @NonNull String packageName,
- boolean hasForegroundExemption) {
- return isRestricted(uid, packageName, /*useTempExemptionListToo=*/ true,
- hasForegroundExemption);
- }
-
- /**
- * @return whether foreground services should be suppressed in the background
- * due to forced app standby for the given app
- */
- public boolean areForegroundServicesRestricted(int uid, @NonNull String packageName) {
- synchronized (mLock) {
- return isRunAnyRestrictedLocked(uid, packageName);
- }
- }
-
- /**
- * @return whether force-app-standby is effective for a UID package-name.
- */
- private boolean isRestricted(int uid, @NonNull String packageName,
- boolean useTempExemptionListToo, boolean exemptOnBatterySaver) {
+ public boolean areAlarmsRestricted(int uid, @NonNull String packageName) {
if (isUidActive(uid)) {
return false;
}
@@ -1149,13 +1132,51 @@
if (ArrayUtils.contains(mPowerExemptAllAppIds, appId)) {
return false;
}
- if (useTempExemptionListToo && ArrayUtils.contains(mTempExemptAppIds, appId)) {
+ return (mForcedAppStandbyEnabled && isRunAnyRestrictedLocked(uid, packageName));
+ }
+ }
+
+ /**
+ * @return whether alarms should be restricted when due to battery saver.
+ */
+ public boolean areAlarmsRestrictedByBatterySaver(int uid, @NonNull String packageName) {
+ if (isUidActive(uid)) {
+ return false;
+ }
+ synchronized (mLock) {
+ final int appId = UserHandle.getAppId(uid);
+ if (ArrayUtils.contains(mPowerExemptAllAppIds, appId)) {
+ return false;
+ }
+ final int userId = UserHandle.getUserId(uid);
+ if (mAppStandbyInternal.isAppIdleEnabled() && !mAppStandbyInternal.isInParole()
+ && mExemptedBucketPackages.contains(userId, packageName)) {
+ return false;
+ }
+ return mForceAllAppsStandby;
+ }
+ }
+
+
+ /**
+ * @return whether jobs should be restricted for a UID package-name. This could be due to
+ * battery saver or user-forced app standby
+ */
+ public boolean areJobsRestricted(int uid, @NonNull String packageName,
+ boolean hasForegroundExemption) {
+ if (isUidActive(uid)) {
+ return false;
+ }
+ synchronized (mLock) {
+ final int appId = UserHandle.getAppId(uid);
+ if (ArrayUtils.contains(mPowerExemptAllAppIds, appId)
+ || ArrayUtils.contains(mTempExemptAppIds, appId)) {
return false;
}
if (mForcedAppStandbyEnabled && isRunAnyRestrictedLocked(uid, packageName)) {
return true;
}
- if (exemptOnBatterySaver) {
+ if (hasForegroundExemption) {
return false;
}
final int userId = UserHandle.getUserId(uid);
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java b/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
index a8c0f0e..657c368 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
@@ -42,7 +42,7 @@
*/
class Alarm {
@VisibleForTesting
- public static final int NUM_POLICIES = 3;
+ public static final int NUM_POLICIES = 4;
/**
* Index used to store the time the alarm was requested to expire. To be used with
* {@link #setPolicyElapsed(int, long)}.
@@ -59,6 +59,12 @@
*/
public static final int DEVICE_IDLE_POLICY_INDEX = 2;
+ /**
+ * Index used to store the earliest time the alarm can expire based on battery saver policy.
+ * To be used with {@link #setPolicyElapsed(int, long)}.
+ */
+ public static final int BATTERY_SAVER_POLICY_INDEX = 3;
+
public final int type;
/**
* The original trigger time supplied by the caller. This can be in the elapsed or rtc time base
@@ -223,6 +229,8 @@
return "app_standby";
case DEVICE_IDLE_POLICY_INDEX:
return "device_idle";
+ case BATTERY_SAVER_POLICY_INDEX:
+ return "battery_saver";
default:
return "--unknown--";
}
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 7842d48..aa46cfd 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -28,6 +28,7 @@
import static android.os.UserHandle.USER_SYSTEM;
import static com.android.server.alarm.Alarm.APP_STANDBY_POLICY_INDEX;
+import static com.android.server.alarm.Alarm.BATTERY_SAVER_POLICY_INDEX;
import static com.android.server.alarm.Alarm.DEVICE_IDLE_POLICY_INDEX;
import static com.android.server.alarm.Alarm.REQUESTER_POLICY_INDEX;
@@ -156,6 +157,7 @@
static final int TICK_HISTORY_DEPTH = 10;
static final long MILLIS_IN_DAY = 24 * 60 * 60 * 1000;
+ static final long INDEFINITE_DELAY = 365 * MILLIS_IN_DAY;
// Indices into the KEYS_APP_STANDBY_QUOTAS array.
static final int ACTIVE_INDEX = 0;
@@ -964,8 +966,7 @@
* Check all alarms in {@link #mPendingBackgroundAlarms} and send the ones that are not
* restricted.
*
- * This is only called when the global "force all apps-standby" flag changes or when the
- * power save whitelist changes, so it's okay to be slow.
+ * This is only called when the power save whitelist changes, so it's okay to be slow.
*/
void sendAllUnrestrictedPendingBackgroundAlarmsLocked() {
final ArrayList<Alarm> alarmsToDeliver = new ArrayList<>();
@@ -984,7 +985,6 @@
Predicate<Alarm> isBackgroundRestricted) {
for (int uidIndex = pendingAlarms.size() - 1; uidIndex >= 0; uidIndex--) {
- final int uid = pendingAlarms.keyAt(uidIndex);
final ArrayList<Alarm> alarmsForUid = pendingAlarms.valueAt(uidIndex);
for (int alarmIndex = alarmsForUid.size() - 1; alarmIndex >= 0; alarmIndex--) {
@@ -1620,6 +1620,44 @@
}
/**
+ * Adjusts the delivery time of the alarm based on battery saver rules.
+ *
+ * @param alarm The alarm to adjust
+ * @return {@code true} if the alarm delivery time was updated.
+ */
+ private boolean adjustDeliveryTimeBasedOnBatterySaver(Alarm alarm) {
+ final long nowElapsed = mInjector.getElapsedRealtime();
+ if (isExemptFromBatterySaver(alarm)) {
+ return false;
+ }
+
+ if (!(mAppStateTracker != null && mAppStateTracker.areAlarmsRestrictedByBatterySaver(
+ alarm.creatorUid, alarm.sourcePackage))) {
+ return alarm.setPolicyElapsed(BATTERY_SAVER_POLICY_INDEX, nowElapsed);
+ }
+
+ final long batterSaverPolicyElapsed;
+ if ((alarm.flags & (AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED)) != 0) {
+ // Unrestricted.
+ batterSaverPolicyElapsed = nowElapsed;
+ } else if ((alarm.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) {
+ // Allowed but limited.
+ final long minDelay;
+ if (mUseAllowWhileIdleShortTime.get(alarm.creatorUid)) {
+ minDelay = mConstants.ALLOW_WHILE_IDLE_SHORT_TIME;
+ } else {
+ minDelay = mConstants.ALLOW_WHILE_IDLE_LONG_TIME;
+ }
+ final long lastDispatch = mLastAllowWhileIdleDispatch.get(alarm.creatorUid, 0);
+ batterSaverPolicyElapsed = (lastDispatch == 0) ? nowElapsed : lastDispatch + minDelay;
+ } else {
+ // Not allowed.
+ batterSaverPolicyElapsed = nowElapsed + INDEFINITE_DELAY;
+ }
+ return alarm.setPolicyElapsed(BATTERY_SAVER_POLICY_INDEX, batterSaverPolicyElapsed);
+ }
+
+ /**
* Adjusts the delivery time of the alarm based on device_idle (doze) rules.
*
* @param alarm The alarm to adjust
@@ -1756,6 +1794,7 @@
if (a.alarmClock != null) {
mNextAlarmClockMayChange = true;
}
+ adjustDeliveryTimeBasedOnBatterySaver(a);
adjustDeliveryTimeBasedOnBucketLocked(a);
mAlarmStore.add(a);
rescheduleKernelAlarmsLocked();
@@ -2230,14 +2269,6 @@
pw.print(": ");
final long lastTime = mLastAllowWhileIdleDispatch.valueAt(i);
TimeUtils.formatDuration(lastTime, nowELAPSED, pw);
-
- final long minInterval = getWhileIdleMinIntervalLocked(uid);
- pw.print(" Next allowed:");
- TimeUtils.formatDuration(lastTime + minInterval, nowELAPSED, pw);
- pw.print(" (");
- TimeUtils.formatDuration(minInterval, 0, pw);
- pw.print(")");
-
pw.println();
}
pw.decreaseIndent();
@@ -2511,8 +2542,6 @@
proto.write(AlarmManagerServiceDumpProto.LastAllowWhileIdleDispatch.UID, uid);
proto.write(AlarmManagerServiceDumpProto.LastAllowWhileIdleDispatch.TIME_MS,
lastTime);
- proto.write(AlarmManagerServiceDumpProto.LastAllowWhileIdleDispatch.NEXT_ALLOWED_MS,
- lastTime + getWhileIdleMinIntervalLocked(uid));
proto.end(token);
}
@@ -3119,30 +3148,36 @@
}
}
+ private boolean isExemptFromBatterySaver(Alarm alarm) {
+ if (alarm.alarmClock != null) {
+ return true;
+ }
+ if ((alarm.operation != null)
+ && (alarm.operation.isActivity() || alarm.operation.isForegroundService())) {
+ return true;
+ }
+ if (UserHandle.isCore(alarm.creatorUid)) {
+ return true;
+ }
+ return false;
+ }
+
private boolean isBackgroundRestricted(Alarm alarm) {
- boolean exemptOnBatterySaver = (alarm.flags & FLAG_ALLOW_WHILE_IDLE) != 0;
if (alarm.alarmClock != null) {
// Don't defer alarm clocks
return false;
}
- if (alarm.operation != null) {
- if (alarm.operation.isActivity()) {
- // Don't defer starting actual UI
- return false;
- }
- if (alarm.operation.isForegroundService()) {
- // FG service alarms are nearly as important; consult AST policy
- exemptOnBatterySaver = true;
- }
+ if (alarm.operation != null && alarm.operation.isActivity()) {
+ // Don't defer starting actual UI
+ return false;
}
final String sourcePackage = alarm.sourcePackage;
final int sourceUid = alarm.creatorUid;
if (UserHandle.isCore(sourceUid)) {
return false;
}
- return (mAppStateTracker != null) &&
- mAppStateTracker.areAlarmsRestricted(sourceUid, sourcePackage,
- exemptOnBatterySaver);
+ return (mAppStateTracker != null) && mAppStateTracker.areAlarmsRestricted(sourceUid,
+ sourcePackage);
}
private static native long init();
@@ -3153,46 +3188,10 @@
private static native int setKernelTimezone(long nativeData, int minuteswest);
private static native long getNextAlarm(long nativeData, int type);
- private long getWhileIdleMinIntervalLocked(int uid) {
- final boolean ebs = (mAppStateTracker != null)
- && mAppStateTracker.isForceAllAppsStandbyEnabled();
-
- if (!ebs || mUseAllowWhileIdleShortTime.get(uid)) {
- // if the last allow-while-idle went off while uid was fg, or the uid
- // recently came into fg, don't block the alarm for long.
- return mConstants.ALLOW_WHILE_IDLE_SHORT_TIME;
- }
- return mConstants.ALLOW_WHILE_IDLE_LONG_TIME;
- }
-
boolean triggerAlarmsLocked(ArrayList<Alarm> triggerList, final long nowELAPSED) {
boolean hasWakeup = false;
final ArrayList<Alarm> pendingAlarms = mAlarmStore.removePendingAlarms(nowELAPSED);
for (final Alarm alarm : pendingAlarms) {
- if ((alarm.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) {
- // If this is an ALLOW_WHILE_IDLE alarm, we constrain how frequently the app can
- // schedule such alarms. The first such alarm from an app is always delivered.
- final long lastTime = mLastAllowWhileIdleDispatch.get(alarm.creatorUid, -1);
- final long minTime = lastTime + getWhileIdleMinIntervalLocked(alarm.creatorUid);
- if (lastTime >= 0 && nowELAPSED < minTime) {
- // Whoops, it hasn't been long enough since the last ALLOW_WHILE_IDLE
- // alarm went off for this app. Reschedule the alarm to be in the
- // correct time period.
- alarm.setPolicyElapsed(REQUESTER_POLICY_INDEX, minTime);
- if (RECORD_DEVICE_IDLE_ALARMS) {
- IdleDispatchEntry ent = new IdleDispatchEntry();
- ent.uid = alarm.uid;
- ent.pkg = alarm.operation.getCreatorPackage();
- ent.tag = alarm.operation.getTag("");
- ent.op = "RESCHEDULE";
- ent.elapsedRealtime = nowELAPSED;
- ent.argRealtime = lastTime;
- mAllowWhileIdleDispatches.add(ent);
- }
- setImplLocked(alarm);
- continue;
- }
- }
if (isBackgroundRestricted(alarm)) {
// Alarms with FLAG_WAKE_FROM_IDLE or mPendingIdleUntil alarm are not deferred
if (DEBUG_BG_LIMIT) {
@@ -3924,8 +3923,41 @@
}
private final Listener mForceAppStandbyListener = new Listener() {
+
+ @Override
+ public void updateAllAlarms() {
+ // Called when:
+ // 1. Power exemption list changes,
+ // 2. Battery saver state is toggled,
+ // 3. Any package is moved into or out of the EXEMPTED bucket.
+ synchronized (mLock) {
+ if (mAlarmStore.updateAlarmDeliveries(
+ a -> adjustDeliveryTimeBasedOnBatterySaver(a))) {
+ rescheduleKernelAlarmsLocked();
+ }
+ }
+ }
+
+ @Override
+ public void updateAlarmsForUid(int uid) {
+ // Called when the given uid's state switches b/w active and idle.
+ synchronized (mLock) {
+ if (mAlarmStore.updateAlarmDeliveries(a -> {
+ if (a.creatorUid != uid) {
+ return false;
+ }
+ return adjustDeliveryTimeBasedOnBatterySaver(a);
+ })) {
+ rescheduleKernelAlarmsLocked();
+ }
+ }
+ }
+
@Override
public void unblockAllUnrestrictedAlarms() {
+ // Called when:
+ // 1. Power exemption list changes,
+ // 2. User FAS feature is disabled.
synchronized (mLock) {
sendAllUnrestrictedPendingBackgroundAlarmsLocked();
}
@@ -3934,12 +3966,14 @@
@Override
public void unblockAlarmsForUid(int uid) {
synchronized (mLock) {
+ // Called when the given uid becomes active.
sendPendingBackgroundAlarmsLocked(uid, null);
}
}
@Override
public void unblockAlarmsForUidPackage(int uid, String packageName) {
+ // Called when user turns off FAS for this (uid, package).
synchronized (mLock) {
sendPendingBackgroundAlarmsLocked(uid, packageName);
}
@@ -3950,9 +3984,14 @@
synchronized (mLock) {
if (foreground) {
mUseAllowWhileIdleShortTime.put(uid, true);
-
- // Note we don't have to drain the pending while-idle alarms here, because
- // this event should coincide with unblockAlarmsForUid().
+ if (mAlarmStore.updateAlarmDeliveries(a -> {
+ if (a.creatorUid != uid || (a.flags & FLAG_ALLOW_WHILE_IDLE) == 0) {
+ return false;
+ }
+ return adjustDeliveryTimeBasedOnBatterySaver(a);
+ })) {
+ rescheduleKernelAlarmsLocked();
+ }
}
}
}
@@ -4236,18 +4275,20 @@
if (allowWhileIdle) {
// Record the last time this uid handled an ALLOW_WHILE_IDLE alarm.
mLastAllowWhileIdleDispatch.put(alarm.creatorUid, nowELAPSED);
- mAlarmStore.updateAlarmDeliveries(a -> {
- if (a.creatorUid != alarm.creatorUid) {
- return false;
- }
- return adjustDeliveryTimeBasedOnDeviceIdle(a);
- });
if ((mAppStateTracker == null)
|| mAppStateTracker.isUidInForeground(alarm.creatorUid)) {
mUseAllowWhileIdleShortTime.put(alarm.creatorUid, true);
} else {
mUseAllowWhileIdleShortTime.put(alarm.creatorUid, false);
}
+ mAlarmStore.updateAlarmDeliveries(a -> {
+ if (a.creatorUid != alarm.creatorUid
+ || (a.flags & FLAG_ALLOW_WHILE_IDLE) == 0) {
+ return false;
+ }
+ return adjustDeliveryTimeBasedOnDeviceIdle(a)
+ | adjustDeliveryTimeBasedOnBatterySaver(a);
+ });
if (RECORD_DEVICE_IDLE_ALARMS) {
IdleDispatchEntry ent = new IdleDispatchEntry();
ent.uid = alarm.uid;
diff --git a/core/api/current.txt b/core/api/current.txt
index 32134fc..8964c9c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -6925,6 +6925,7 @@
method @NonNull public java.util.List<java.lang.String> getDelegatedScopes(@Nullable android.content.ComponentName, @NonNull String);
method public CharSequence getDeviceOwnerLockScreenInfo();
method public CharSequence getEndUserSessionMessage(@NonNull android.content.ComponentName);
+ method @NonNull public String getEnrollmentSpecificId();
method @Nullable public android.app.admin.FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(@Nullable android.content.ComponentName);
method @Nullable public String getGlobalPrivateDnsHost(@NonNull android.content.ComponentName);
method public int getGlobalPrivateDnsMode(@NonNull android.content.ComponentName);
@@ -7075,6 +7076,7 @@
method @NonNull public java.util.List<java.lang.String> setMeteredDataDisabledPackages(@NonNull android.content.ComponentName, @NonNull java.util.List<java.lang.String>);
method public void setNetworkLoggingEnabled(@Nullable android.content.ComponentName, boolean);
method @Deprecated public void setOrganizationColor(@NonNull android.content.ComponentName, int);
+ method public void setOrganizationId(@NonNull String);
method public void setOrganizationName(@NonNull android.content.ComponentName, @Nullable CharSequence);
method public void setOverrideApnsEnabled(@NonNull android.content.ComponentName, boolean);
method @NonNull public String[] setPackagesSuspended(@NonNull android.content.ComponentName, @NonNull String[], boolean);
@@ -12375,6 +12377,7 @@
field public static final String FEATURE_TOUCHSCREEN_MULTITOUCH = "android.hardware.touchscreen.multitouch";
field public static final String FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT = "android.hardware.touchscreen.multitouch.distinct";
field public static final String FEATURE_TOUCHSCREEN_MULTITOUCH_JAZZHAND = "android.hardware.touchscreen.multitouch.jazzhand";
+ field public static final String FEATURE_TRANSLATION = "android.software.translation";
field public static final String FEATURE_USB_ACCESSORY = "android.hardware.usb.accessory";
field public static final String FEATURE_USB_HOST = "android.hardware.usb.host";
field public static final String FEATURE_VERIFIED_BOOT = "android.software.verified_boot";
@@ -31432,6 +31435,7 @@
method @NonNull public android.os.VibrationEffect.Composition addPrimitive(int, @FloatRange(from=0.0f, to=1.0f) float, @IntRange(from=0) int);
method @NonNull public android.os.VibrationEffect compose();
field public static final int PRIMITIVE_CLICK = 1; // 0x1
+ field public static final int PRIMITIVE_LOW_TICK = 8; // 0x8
field public static final int PRIMITIVE_QUICK_FALL = 6; // 0x6
field public static final int PRIMITIVE_QUICK_RISE = 4; // 0x4
field public static final int PRIMITIVE_SLOW_RISE = 5; // 0x5
@@ -45458,6 +45462,7 @@
method public void remove(int);
method public void removeAt(int);
method public void removeAtRange(int, int);
+ method public void set(int, E);
method public void setValueAt(int, E);
method public int size();
method public E valueAt(int);
@@ -51608,6 +51613,66 @@
}
+package android.view.translation {
+
+ public final class TranslationManager {
+ method @Nullable @WorkerThread public android.view.translation.Translator createTranslator(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec);
+ method @NonNull @WorkerThread public java.util.List<java.lang.String> getSupportedLocales();
+ }
+
+ public final class TranslationRequest implements android.os.Parcelable {
+ ctor public TranslationRequest(@Nullable CharSequence);
+ method public int describeContents();
+ method @Nullable public android.view.autofill.AutofillId getAutofillId();
+ method @Nullable public CharSequence getTranslationText();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.view.translation.TranslationRequest> CREATOR;
+ }
+
+ public static final class TranslationRequest.Builder {
+ ctor public TranslationRequest.Builder();
+ method @NonNull public android.view.translation.TranslationRequest build();
+ method @NonNull public android.view.translation.TranslationRequest.Builder setAutofillId(@NonNull android.view.autofill.AutofillId);
+ method @NonNull public android.view.translation.TranslationRequest.Builder setTranslationText(@NonNull CharSequence);
+ }
+
+ public final class TranslationResponse implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getTranslationStatus();
+ method @NonNull public java.util.List<android.view.translation.TranslationRequest> getTranslations();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.view.translation.TranslationResponse> CREATOR;
+ field public static final int TRANSLATION_STATUS_LANGUAGE_UNAVAILABLE = 2; // 0x2
+ field public static final int TRANSLATION_STATUS_SUCCESS = 0; // 0x0
+ field public static final int TRANSLATION_STATUS_UNKNOWN_ERROR = 1; // 0x1
+ }
+
+ public static final class TranslationResponse.Builder {
+ ctor public TranslationResponse.Builder(int);
+ method @NonNull public android.view.translation.TranslationResponse.Builder addTranslations(@NonNull android.view.translation.TranslationRequest);
+ method @NonNull public android.view.translation.TranslationResponse build();
+ method @NonNull public android.view.translation.TranslationResponse.Builder setTranslationStatus(int);
+ method @NonNull public android.view.translation.TranslationResponse.Builder setTranslations(@NonNull java.util.List<android.view.translation.TranslationRequest>);
+ }
+
+ public final class TranslationSpec implements android.os.Parcelable {
+ ctor public TranslationSpec(@NonNull String, int);
+ method public int describeContents();
+ method public int getDataFormat();
+ method @NonNull public String getLanguage();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.view.translation.TranslationSpec> CREATOR;
+ field public static final int DATA_FORMAT_TEXT = 1; // 0x1
+ }
+
+ public class Translator {
+ method public void destroy();
+ method public boolean isDestroyed();
+ method @Nullable @WorkerThread public android.view.translation.TranslationResponse translate(@NonNull android.view.translation.TranslationRequest);
+ }
+
+}
+
package android.webkit {
public abstract class ClientCertRequest {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 1f08d96..7bbab71 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -52,6 +52,7 @@
field public static final String BIND_TELEPHONY_NETWORK_SERVICE = "android.permission.BIND_TELEPHONY_NETWORK_SERVICE";
field public static final String BIND_TEXTCLASSIFIER_SERVICE = "android.permission.BIND_TEXTCLASSIFIER_SERVICE";
field public static final String BIND_TIME_ZONE_PROVIDER_SERVICE = "android.permission.BIND_TIME_ZONE_PROVIDER_SERVICE";
+ field public static final String BIND_TRANSLATION_SERVICE = "android.permission.BIND_TRANSLATION_SERVICE";
field public static final String BIND_TRUST_AGENT = "android.permission.BIND_TRUST_AGENT";
field public static final String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE";
field public static final String BRICK = "android.permission.BRICK";
@@ -906,6 +907,7 @@
field public static final int STATE_USER_SETUP_FINALIZED = 3; // 0x3
field public static final int STATE_USER_SETUP_INCOMPLETE = 1; // 0x1
field public static final int STATE_USER_UNMANAGED = 0; // 0x0
+ field public static final int SUPPORTED_MODES_DEVICE_OWNER = 4; // 0x4
field public static final int SUPPORTED_MODES_ORGANIZATION_AND_PERSONALLY_OWNED = 3; // 0x3
field public static final int SUPPORTED_MODES_ORGANIZATION_OWNED = 1; // 0x1
field public static final int SUPPORTED_MODES_PERSONALLY_OWNED = 2; // 0x2
@@ -1940,6 +1942,7 @@
field public static final String SYSTEM_CONFIG_SERVICE = "system_config";
field public static final String SYSTEM_UPDATE_SERVICE = "system_update";
field public static final String TETHERING_SERVICE = "tethering";
+ field public static final String TRANSLATION_MANAGER_SERVICE = "transformer";
field public static final String VR_SERVICE = "vrmanager";
field public static final String WIFI_NL80211_SERVICE = "wifinl80211";
field @Deprecated public static final String WIFI_RTT_SERVICE = "rttmanager";
@@ -6913,6 +6916,7 @@
field public static final int NET_CAPABILITY_OEM_PAID = 22; // 0x16
field public static final int NET_CAPABILITY_OEM_PRIVATE = 26; // 0x1a
field public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24; // 0x18
+ field public static final int NET_CAPABILITY_VEHICLE_INTERNAL = 27; // 0x1b
}
public static final class NetworkCapabilities.Builder {
@@ -8554,7 +8558,7 @@
package android.provider {
public class CallLog {
- method @RequiresPermission(android.Manifest.permission.WRITE_CALL_LOG) public static void storeCallComposerPictureAsUser(@NonNull android.content.Context, @Nullable android.os.UserHandle, @NonNull java.io.InputStream, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.net.Uri,android.provider.CallLog.CallComposerLoggingException>);
+ method @RequiresPermission(allOf={android.Manifest.permission.WRITE_CALL_LOG, android.Manifest.permission.INTERACT_ACROSS_USERS}) public static void storeCallComposerPictureAsUser(@NonNull android.content.Context, @Nullable android.os.UserHandle, @NonNull java.io.InputStream, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.net.Uri,android.provider.CallLog.CallComposerLoggingException>);
}
public static class CallLog.CallComposerLoggingException extends java.lang.Throwable {
@@ -9853,6 +9857,48 @@
}
+package android.service.translation {
+
+ public final class TranslationRequest implements android.os.Parcelable {
+ ctor public TranslationRequest(int, @NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, @NonNull java.util.List<android.view.translation.TranslationRequest>);
+ method public int describeContents();
+ method @NonNull public android.view.translation.TranslationSpec getDestSpec();
+ method public int getRequestId();
+ method @NonNull public android.view.translation.TranslationSpec getSourceSpec();
+ method @NonNull public java.util.List<android.view.translation.TranslationRequest> getTranslationRequests();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.translation.TranslationRequest> CREATOR;
+ }
+
+ public static final class TranslationRequest.Builder {
+ ctor public TranslationRequest.Builder(int, @NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, @NonNull java.util.List<android.view.translation.TranslationRequest>);
+ method @NonNull public android.service.translation.TranslationRequest.Builder addTranslationRequests(@NonNull android.view.translation.TranslationRequest);
+ method @NonNull public android.service.translation.TranslationRequest build();
+ method @NonNull public android.service.translation.TranslationRequest.Builder setDestSpec(@NonNull android.view.translation.TranslationSpec);
+ method @NonNull public android.service.translation.TranslationRequest.Builder setRequestId(int);
+ method @NonNull public android.service.translation.TranslationRequest.Builder setSourceSpec(@NonNull android.view.translation.TranslationSpec);
+ method @NonNull public android.service.translation.TranslationRequest.Builder setTranslationRequests(@NonNull java.util.List<android.view.translation.TranslationRequest>);
+ }
+
+ public abstract class TranslationService extends android.app.Service {
+ ctor public TranslationService();
+ method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
+ method public void onConnected();
+ method public abstract void onCreateTranslationSession(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, int);
+ method public void onDisconnected();
+ method public abstract void onFinishTranslationSession(int);
+ method public abstract void onTranslationRequest(@NonNull android.service.translation.TranslationRequest, int, @NonNull android.os.CancellationSignal, @NonNull android.service.translation.TranslationService.OnTranslationResultCallback);
+ field public static final String SERVICE_INTERFACE = "android.service.translation.TranslationService";
+ field public static final String SERVICE_META_DATA = "android.translation_service";
+ }
+
+ public static interface TranslationService.OnTranslationResultCallback {
+ method public void onError();
+ method public void onTranslationSuccess(@NonNull android.view.translation.TranslationResponse);
+ }
+
+}
+
package android.service.trust {
public class TrustAgentService extends android.app.Service {
@@ -11207,6 +11253,7 @@
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();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isPotentialEmergencyNumber(@NonNull String);
+ method public boolean isRadioInterfaceCapabilitySupported(@NonNull String);
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRadioOn();
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRinging();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean isTetheringApnRequired();
@@ -11281,6 +11328,7 @@
field public static final int CALL_WAITING_STATUS_ENABLED = 1; // 0x1
field public static final int CALL_WAITING_STATUS_NOT_SUPPORTED = 4; // 0x4
field public static final int CALL_WAITING_STATUS_UNKNOWN_ERROR = 3; // 0x3
+ field public static final String CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE = "CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE";
field public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe
field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index c03461a..1422561 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2512,7 +2512,6 @@
method @CallSuper @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public java.util.List<android.window.TaskAppearedInfo> registerOrganizer();
method @BinderThread public void removeStartingWindow(int);
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void setInterceptBackPressedOnTaskRoot(@NonNull android.window.WindowContainerToken, boolean);
- method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void setLaunchRoot(int, @NonNull android.window.WindowContainerToken);
method @CallSuper @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void unregisterOrganizer();
}
@@ -2527,6 +2526,7 @@
method public int describeContents();
method @NonNull public android.window.WindowContainerTransaction reorder(@NonNull android.window.WindowContainerToken, boolean);
method @NonNull public android.window.WindowContainerTransaction reparent(@NonNull android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken, boolean);
+ method @NonNull public android.window.WindowContainerTransaction reparentTasks(@Nullable android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken, @Nullable int[], @Nullable int[], boolean);
method @NonNull public android.window.WindowContainerTransaction scheduleFinishEnterPip(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
method @NonNull public android.window.WindowContainerTransaction setActivityWindowingMode(@NonNull android.window.WindowContainerToken, int);
method @NonNull public android.window.WindowContainerTransaction setAppBounds(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
@@ -2534,6 +2534,7 @@
method @NonNull public android.window.WindowContainerTransaction setBoundsChangeTransaction(@NonNull android.window.WindowContainerToken, @NonNull android.view.SurfaceControl.Transaction);
method @NonNull public android.window.WindowContainerTransaction setFocusable(@NonNull android.window.WindowContainerToken, boolean);
method @NonNull public android.window.WindowContainerTransaction setHidden(@NonNull android.window.WindowContainerToken, boolean);
+ method @NonNull public android.window.WindowContainerTransaction setLaunchRoot(@NonNull android.window.WindowContainerToken, @Nullable int[], @Nullable int[]);
method @NonNull public android.window.WindowContainerTransaction setScreenSizeDp(@NonNull android.window.WindowContainerToken, int, int);
method @NonNull public android.window.WindowContainerTransaction setSmallestScreenWidthDp(@NonNull android.window.WindowContainerToken, int);
method @NonNull public android.window.WindowContainerTransaction setWindowingMode(@NonNull android.window.WindowContainerToken, int);
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 6e7bb83..db83813 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -36,6 +36,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* Activity manager local system service interface.
@@ -447,6 +448,22 @@
public abstract void setDeviceOwnerUid(int uid);
/**
+ * Set all associated companion app that belongs to a userId.
+ * @param userId
+ * @param companionAppUids ActivityManager will take ownership of this Set, the caller
+ * shouldn't touch this Set after calling this interface.
+ */
+ public abstract void setCompanionAppUids(int userId, Set<Integer> companionAppUids);
+
+ /**
+ * is the uid an associated companion app of a userId?
+ * @param userId
+ * @param uid
+ * @return
+ */
+ public abstract boolean isAssociatedCompanionApp(int userId, int uid);
+
+ /**
* Sends a broadcast, assuming the caller to be the system and allowing the inclusion of an
* approved whitelist of app Ids >= {@link android.os.Process#FIRST_APPLICATION_UID} that the
* broadcast my be sent to; any app Ids < {@link android.os.Process#FIRST_APPLICATION_UID} are
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 06ad9c9..6d79e2d 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -2,6 +2,33 @@
# Remain no owner because multiple modules may touch this file.
per-file ContextImpl.java = *
+# ActivityManager
+per-file ActivityManager* = file:/services/core/java/com/android/server/am/OWNERS
+per-file ApplicationErrorReport* = file:/services/core/java/com/android/server/am/OWNERS
+per-file ApplicationExitInfo* = file:/services/core/java/com/android/server/am/OWNERS
+per-file Application.java = file:/services/core/java/com/android/server/am/OWNERS
+per-file ApplicationLoaders.java = file:/services/core/java/com/android/server/am/OWNERS
+per-file ApplicationThreadConstants.java = file:/services/core/java/com/android/server/am/OWNERS
+per-file BroadcastOptions.java = file:/services/core/java/com/android/server/am/OWNERS
+per-file ContentProviderHolder* = file:/services/core/java/com/android/server/am/OWNERS
+per-file IActivityController.aidl = file:/services/core/java/com/android/server/am/OWNERS
+per-file IActivityManager.aidl = file:/services/core/java/com/android/server/am/OWNERS
+per-file IApplicationThread.aidl = file:/services/core/java/com/android/server/am/OWNERS
+per-file IAppTraceRetriever.aidl = file:/services/core/java/com/android/server/am/OWNERS
+per-file IInstrumentationWatcher.aidl = file:/services/core/java/com/android/server/am/OWNERS
+per-file IntentService.aidl = file:/services/core/java/com/android/server/am/OWNERS
+per-file IServiceConnection.aidl = file:/services/core/java/com/android/server/am/OWNERS
+per-file IStopUserCallback.aidl = file:/services/core/java/com/android/server/am/OWNERS
+per-file IUidObserver.aidl = file:/services/core/java/com/android/server/am/OWNERS
+per-file LoadedApk.java = file:/services/core/java/com/android/server/am/OWNERS
+per-file LocalActivityManager.java = file:/services/core/java/com/android/server/am/OWNERS
+per-file PendingIntent* = file:/services/core/java/com/android/server/am/OWNERS
+per-file *Process* = file:/services/core/java/com/android/server/am/OWNERS
+per-file ProfilerInfo* = file:/services/core/java/com/android/server/am/OWNERS
+per-file Service* = file:/services/core/java/com/android/server/am/OWNERS
+per-file SystemServiceRegistry.java = file:/services/core/java/com/android/server/am/OWNERS
+per-file *UserSwitchObserver* = file:/services/core/java/com/android/server/am/OWNERS
+
# ActivityThread
per-file ActivityThread.java = file:/services/core/java/com/android/server/am/OWNERS
per-file ActivityThread.java = file:/services/core/java/com/android/server/wm/OWNERS
@@ -12,6 +39,9 @@
# AppOps
per-file *AppOp* = file:/core/java/android/permission/OWNERS
+# Multiuser
+per-file *User* = file:/MULTIUSER_OWNERS
+
# Notification
per-file *Notification* = file:/packages/SystemUI/OWNERS
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 050d194..dd016a2 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -209,6 +209,8 @@
import android.view.inputmethod.InputMethodManager;
import android.view.textclassifier.TextClassificationManager;
import android.view.textservice.TextServicesManager;
+import android.view.translation.ITranslationManager;
+import android.view.translation.TranslationManager;
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IBatteryStats;
@@ -1172,6 +1174,20 @@
return null;
}});
+ registerService(Context.TRANSLATION_MANAGER_SERVICE, TranslationManager.class,
+ new CachedServiceFetcher<TranslationManager>() {
+ @Override
+ public TranslationManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getService(Context.TRANSLATION_MANAGER_SERVICE);
+ ITranslationManager service = ITranslationManager.Stub.asInterface(b);
+ // Service is null when not provided by OEM.
+ if (service != null) {
+ return new TranslationManager(ctx.getOuterContext(), service);
+ }
+ return null;
+ }});
+
registerService(Context.SEARCH_UI_SERVICE, SearchUiManager.class,
new CachedServiceFetcher<SearchUiManager>() {
@Override
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 6b997f0..7bc967f 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -39,7 +39,6 @@
import android.app.Activity;
import android.app.IServiceConnection;
import android.app.KeyguardManager;
-import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
import android.app.admin.SecurityLog.SecurityEvent;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
@@ -1257,7 +1256,8 @@
@IntDef(prefix = { "SUPPORTED_MODES_" }, value = {
SUPPORTED_MODES_ORGANIZATION_OWNED,
SUPPORTED_MODES_PERSONALLY_OWNED,
- SUPPORTED_MODES_ORGANIZATION_AND_PERSONALLY_OWNED
+ SUPPORTED_MODES_ORGANIZATION_AND_PERSONALLY_OWNED,
+ SUPPORTED_MODES_DEVICE_OWNER
})
@Retention(RetentionPolicy.SOURCE)
public @interface ProvisioningConfiguration {}
@@ -1384,6 +1384,15 @@
public static final int SUPPORTED_MODES_ORGANIZATION_AND_PERSONALLY_OWNED = 3;
/**
+ * A value for {@link #EXTRA_PROVISIONING_SUPPORTED_MODES} indicating that the only supported
+ * provisioning mode is device owner.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int SUPPORTED_MODES_DEVICE_OWNER = 4;
+
+ /**
* This MIME type is used for starting the device owner provisioning.
*
* <p>During device owner provisioning a device admin app is set as the owner of the device.
@@ -2519,6 +2528,7 @@
* @see #SUPPORTED_MODES_ORGANIZATION_OWNED
* @see #SUPPORTED_MODES_PERSONALLY_OWNED
* @see #SUPPORTED_MODES_ORGANIZATION_AND_PERSONALLY_OWNED
+ * @see #SUPPORTED_MODES_DEVICE_OWNER
* @hide
*/
@SystemApi
@@ -2719,6 +2729,17 @@
return DebugUtils.constantToString(DevicePolicyManager.class, PREFIX_OPERATION, operation);
}
+ /** @hide */
+ public void resetNewUserDisclaimer() {
+ if (mService != null) {
+ try {
+ mService.resetNewUserDisclaimer();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
/**
* Return true if the given administrator component is currently active (enabled) in the system.
*
@@ -3010,6 +3031,10 @@
*
* <p><strong>Note:</strong> Specifying password requirements using this method clears the
* password complexity requirements set using {@link #setRequiredPasswordComplexity(int)}.
+ * If this method is called on the {@link DevicePolicyManager} instance returned by
+ * {@link #getParentProfileInstance(ComponentName)}, then password complexity requirements
+ * set on the primary {@link DevicePolicyManager} must be cleared first by calling
+ * {@link #setRequiredPasswordComplexity} with {@link #PASSWORD_COMPLEXITY_NONE) first.
*
* @deprecated Prefer using {@link #setRequiredPasswordComplexity(int)}, to require a password
* that satisfies a complexity level defined by the platform, rather than specifying custom
@@ -3029,6 +3054,9 @@
* calling app is targeting {@link android.os.Build.VERSION_CODES#S} and above,
* and is calling the method the {@link DevicePolicyManager} instance returned by
* {@link #getParentProfileInstance(ComponentName)}.
+ * @throws IllegalStateException if the caller is trying to set password quality on the parent
+ * {@link DevicePolicyManager} instance while password complexity was set on the
+ * primary {@link DevicePolicyManager} instance.
*/
@Deprecated
public void setPasswordQuality(@NonNull ComponentName admin, int quality) {
@@ -4045,10 +4073,18 @@
* <p><strong>Note:</strong> Specifying password requirements using this method clears any
* password requirements set using the obsolete {@link #setPasswordQuality(ComponentName, int)}
* and any of its associated methods.
+ * Additionally, if there are password requirements set using the obsolete
+ * {@link #setPasswordQuality(ComponentName, int)} on the parent {@code DevicePolicyManager}
+ * instance, they must be cleared by calling {@link #setPasswordQuality(ComponentName, int)}
+ * with {@link #PASSWORD_QUALITY_UNSPECIFIED} on that instance prior to setting complexity
+ * requirement for the managed profile.
*
* @throws SecurityException if the calling application is not a device owner or a profile
* owner.
* @throws IllegalArgumentException if the complexity level is not one of the four above.
+ * @throws IllegalStateException if the caller is trying to set password complexity while there
+ * are password requirements specified using {@link #setPasswordQuality(ComponentName, int)}
+ * on the parent {@code DevicePolicyManager} instance.
*/
public void setRequiredPasswordComplexity(@PasswordComplexity int passwordComplexity) {
if (mService == null) {
@@ -5181,6 +5217,16 @@
"android.app.action.MANAGED_USER_CREATED";
/**
+ * Broadcast action: notify system that a new (Android) user was added when the device is
+ * managed by a device owner, so receivers can show the proper disclaimer to the (human) user.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_SHOW_NEW_USER_DISCLAIMER =
+ "android.app.action.ACTION_SHOW_NEW_USER_DISCLAIMER";
+
+ /**
* Widgets are enabled in keyguard
*/
public static final int KEYGUARD_DISABLE_FEATURES_NONE = 0;
@@ -12803,4 +12849,66 @@
}
}
}
+
+ /**
+ * Returns an enrollment-specific identifier of this device, which is guaranteed to be the same
+ * value for the same device, enrolled into the same organization by the same managing app.
+ * This identifier is high-entropy, useful for uniquely identifying individual devices within
+ * the same organisation.
+ * It is available both in a work profile and on a fully-managed device.
+ * The identifier would be consistent even if the work profile is removed and enrolled again
+ * (to the same organization), or the device is factory reset and re-enrolled.
+
+ * Can only be called by the Profile Owner or Device Owner, if the
+ * {@link #setOrganizationId(String)} was previously called.
+ * If {@link #setOrganizationId(String)} was not called, then the returned value will be an
+ * empty string.
+ *
+ * @return A stable, enrollment-specific identifier.
+ * @throws SecurityException if the caller is not a profile owner or device owner.
+ */
+ @NonNull public String getEnrollmentSpecificId() {
+ if (mService == null) {
+ return "";
+ }
+
+ try {
+ return mService.getEnrollmentSpecificId();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sets the Enterprise ID for the work profile or managed device. This is a requirement for
+ * generating an enrollment-specific ID for the device, see {@link #getEnrollmentSpecificId()}.
+ *
+ * It is recommended that the Enterprise ID is at least 6 characters long, and no more than
+ * 64 characters.
+ *
+ * @param enterpriseId An identifier of the organization this work profile or device is
+ * enrolled into.
+ */
+ public void setOrganizationId(@NonNull String enterpriseId) {
+ setOrganizationIdForUser(mContext.getPackageName(), enterpriseId, myUserId());
+ }
+
+ /**
+ * Sets the Enterprise ID for the work profile or managed device. This is a requirement for
+ * generating an enrollment-specific ID for the device, see
+ * {@link #getEnrollmentSpecificId()}.
+ *
+ * @hide
+ */
+ public void setOrganizationIdForUser(@NonNull String packageName,
+ @NonNull String enterpriseId, @UserIdInt int userId) {
+ if (mService == null) {
+ return;
+ }
+ try {
+ mService.setOrganizationIdForUser(packageName, enterpriseId, userId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 4b87bb9..aaa5f7c 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -252,6 +252,7 @@
int stopUser(in ComponentName who, in UserHandle userHandle);
int logoutUser(in ComponentName who);
List<UserHandle> getSecondaryUsers(in ComponentName who);
+ void resetNewUserDisclaimer();
void enableSystemApp(in ComponentName admin, in String callerPackage, in String packageName);
int enableSystemAppWithIntent(in ComponentName admin, in String callerPackage, in Intent intent);
@@ -489,4 +490,7 @@
boolean canProfileOwnerResetPasswordWhenLocked(int userId);
void setNextOperationSafety(int operation, boolean safe);
+
+ String getEnrollmentSpecificId();
+ void setOrganizationIdForUser(in String callerPackage, in String enterpriseId, int userId);
}
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index 7a6ff79..381318b 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -824,6 +824,25 @@
* error
*/
private boolean registerApp(BluetoothGattCallback callback, Handler handler) {
+ return registerApp(callback, handler, false);
+ }
+
+ /**
+ * Register an application callback to start using GATT.
+ *
+ * <p>This is an asynchronous call. The callback {@link BluetoothGattCallback#onAppRegistered}
+ * is used to notify success or failure if the function returns true.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param callback GATT callback handler that will receive asynchronous callbacks.
+ * @param eatt_support indicate to allow for eatt support
+ * @return If true, the callback will be called to notify success or failure, false on immediate
+ * error
+ * @hide
+ */
+ private boolean registerApp(BluetoothGattCallback callback, Handler handler,
+ boolean eatt_support) {
if (DBG) Log.d(TAG, "registerApp()");
if (mService == null) return false;
@@ -833,7 +852,7 @@
if (DBG) Log.d(TAG, "registerApp() - UUID=" + uuid);
try {
- mService.registerClient(new ParcelUuid(uuid), mBluetoothGattCallback);
+ mService.registerClient(new ParcelUuid(uuid), mBluetoothGattCallback, eatt_support);
} catch (RemoteException e) {
Log.e(TAG, "", e);
return false;
diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java
index 13b1b4f..088b016 100644
--- a/core/java/android/bluetooth/BluetoothGattServer.java
+++ b/core/java/android/bluetooth/BluetoothGattServer.java
@@ -443,6 +443,25 @@
* error
*/
/*package*/ boolean registerCallback(BluetoothGattServerCallback callback) {
+ return registerCallback(callback, false);
+ }
+
+ /**
+ * Register an application callback to start using GattServer.
+ *
+ * <p>This is an asynchronous call. The callback is used to notify
+ * success or failure if the function returns true.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param callback GATT callback handler that will receive asynchronous callbacks.
+ * @param eatt_support indicates if server can use eatt
+ * @return true, the callback will be called to notify success or failure, false on immediate
+ * error
+ * @hide
+ */
+ /*package*/ boolean registerCallback(BluetoothGattServerCallback callback,
+ boolean eatt_support) {
if (DBG) Log.d(TAG, "registerCallback()");
if (mService == null) {
Log.e(TAG, "GATT service not available");
@@ -459,7 +478,7 @@
mCallback = callback;
try {
- mService.registerServer(new ParcelUuid(uuid), mBluetoothGattServerCallback);
+ mService.registerServer(new ParcelUuid(uuid), mBluetoothGattServerCallback, eatt_support);
} catch (RemoteException e) {
Log.e(TAG, "", e);
mCallback = null;
diff --git a/core/java/android/bluetooth/BluetoothManager.java b/core/java/android/bluetooth/BluetoothManager.java
index 3b4fe0a..d5c1c3e 100644
--- a/core/java/android/bluetooth/BluetoothManager.java
+++ b/core/java/android/bluetooth/BluetoothManager.java
@@ -225,6 +225,24 @@
*
* @param context App context
* @param callback GATT server callback handler that will receive asynchronous callbacks.
+ * @param eatt_support idicates if server should use eatt channel for notifications.
+ * @return BluetoothGattServer instance
+ * @hide
+ */
+ public BluetoothGattServer openGattServer(Context context,
+ BluetoothGattServerCallback callback, boolean eatt_support) {
+ return (openGattServer(context, callback, BluetoothDevice.TRANSPORT_AUTO, eatt_support));
+ }
+
+ /**
+ * Open a GATT Server
+ * The callback is used to deliver results to Caller, such as connection status as well
+ * as the results of any other GATT server operations.
+ * The method returns a BluetoothGattServer instance. You can use BluetoothGattServer
+ * to conduct GATT server operations.
+ *
+ * @param context App context
+ * @param callback GATT server callback handler that will receive asynchronous callbacks.
* @param transport preferred transport for GATT connections to remote dual-mode devices {@link
* BluetoothDevice#TRANSPORT_AUTO} or {@link BluetoothDevice#TRANSPORT_BREDR} or {@link
* BluetoothDevice#TRANSPORT_LE}
@@ -233,6 +251,27 @@
*/
public BluetoothGattServer openGattServer(Context context,
BluetoothGattServerCallback callback, int transport) {
+ return (openGattServer(context, callback, transport, false));
+ }
+
+ /**
+ * Open a GATT Server
+ * The callback is used to deliver results to Caller, such as connection status as well
+ * as the results of any other GATT server operations.
+ * The method returns a BluetoothGattServer instance. You can use BluetoothGattServer
+ * to conduct GATT server operations.
+ *
+ * @param context App context
+ * @param callback GATT server callback handler that will receive asynchronous callbacks.
+ * @param transport preferred transport for GATT connections to remote dual-mode devices {@link
+ * BluetoothDevice#TRANSPORT_AUTO} or {@link BluetoothDevice#TRANSPORT_BREDR} or {@link
+ * BluetoothDevice#TRANSPORT_LE}
+ * @param eatt_support idicates if server should use eatt channel for notifications.
+ * @return BluetoothGattServer instance
+ * @hide
+ */
+ public BluetoothGattServer openGattServer(Context context,
+ BluetoothGattServerCallback callback, int transport, boolean eatt_support) {
if (context == null || callback == null) {
throw new IllegalArgumentException("null parameter: " + context + " " + callback);
}
@@ -248,7 +287,7 @@
return null;
}
BluetoothGattServer mGattServer = new BluetoothGattServer(iGatt, transport);
- Boolean regStatus = mGattServer.registerCallback(callback);
+ Boolean regStatus = mGattServer.registerCallback(callback, eatt_support);
return regStatus ? mGattServer : null;
} catch (RemoteException e) {
Log.e(TAG, "", e);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 43011fc..5ccceca 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4509,6 +4509,17 @@
public static final String CONTENT_CAPTURE_MANAGER_SERVICE = "content_capture";
/**
+ * Official published name of the translation service.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ */
+ // TODO(b/176208267): change it back to translation before S release.
+ @SystemApi
+ @SuppressLint("ServiceName")
+ public static final String TRANSLATION_MANAGER_SERVICE = "transformer";
+
+ /**
* Used for getting content selections and classifications for task snapshots.
*
* @hide
diff --git a/core/java/android/content/pm/OWNERS b/core/java/android/content/pm/OWNERS
index f88df95..fd32efc 100644
--- a/core/java/android/content/pm/OWNERS
+++ b/core/java/android/content/pm/OWNERS
@@ -7,3 +7,4 @@
per-file PackageParser.java = chiuwinson@google.com
per-file *Shortcut* = file:/core/java/android/content/pm/SHORTCUT_OWNERS
per-file *Launcher* = file:/core/java/android/content/pm/LAUNCHER_OWNERS
+per-file UserInfo* = file:/MULTIUSER_OWNERS
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index e074eab..17c4d25 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3406,6 +3406,14 @@
/**
* Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device supports translation of text-to-text in multiple languages via integration with
+ * the system {@link android.service.translation.TranslationService translation provider}.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_TRANSLATION = "android.software.translation";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
* The device implements headtracking suitable for a VR device.
*/
@SdkConstant(SdkConstantType.FEATURE)
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index 987d790..4145a72 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -358,6 +358,27 @@
}
/**
+ * Requests all {@link Authenticators.Types#BIOMETRIC_STRONG} sensors to have their
+ * authenticatorId invalidated for the specified user. This happens when enrollments have been
+ * added on devices with multiple biometric sensors.
+ *
+ * @param userId userId that the authenticatorId should be invalidated for
+ * @param fromSensorId sensor that triggered the invalidation request
+ * @hide
+ */
+ @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+ public void invalidateAuthenticatorIds(int userId, int fromSensorId,
+ @NonNull IInvalidationCallback callback) {
+ if (mService != null) {
+ try {
+ mService.invalidateAuthenticatorIds(userId, fromSensorId, callback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
* Get a list of AuthenticatorIDs for biometric authenticators which have 1) enrolled templates,
* and 2) meet the requirements for integrating with Keystore. The AuthenticatorIDs are known
* in Keystore land as SIDs, and are used during key generation.
diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl
index 8e7f5ce..0dfd5db 100644
--- a/core/java/android/hardware/biometrics/IAuthService.aidl
+++ b/core/java/android/hardware/biometrics/IAuthService.aidl
@@ -18,6 +18,7 @@
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
import android.hardware.biometrics.IBiometricServiceReceiver;
+import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.PromptInfo;
import android.hardware.biometrics.SensorPropertiesInternal;
@@ -57,6 +58,11 @@
// Register callback for when keyguard biometric eligibility changes.
void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback);
+ // Requests all BIOMETRIC_STRONG sensors to have their authenticatorId invalidated for the
+ // specified user. This happens when enrollments have been added on devices with multiple
+ // biometric sensors.
+ void invalidateAuthenticatorIds(int userId, int fromSensorId, IInvalidationCallback callback);
+
// Get a list of AuthenticatorIDs for authenticators which have enrolled templates and meet
// the requirements for integrating with Keystore. The AuthenticatorID are known in Keystore
// land as SIDs, and are used during key generation.
diff --git a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
index cc12125..fcdf61e 100644
--- a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
@@ -18,6 +18,7 @@
import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
+import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.SensorPropertiesInternal;
import android.hardware.face.IFaceServiceReceiver;
@@ -63,6 +64,9 @@
// Return the LockoutTracker status for the specified user
int getLockoutModeForUser(int userId);
+ // Request the authenticatorId to be invalidated for the specified user
+ void invalidateAuthenticatorId(int userId, IInvalidationCallback callback);
+
// Gets the authenticator ID representing the current set of enrolled templates
long getAuthenticatorId(int callingUserId);
}
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index 6f7bcb6..a14a910 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -19,6 +19,7 @@
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.IBiometricAuthenticator;
+import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.PromptInfo;
import android.hardware.biometrics.SensorPropertiesInternal;
@@ -62,6 +63,11 @@
// Client lifecycle is still managed in <Biometric>Service.
void onReadyForAuthentication(int cookie);
+ // Requests all BIOMETRIC_STRONG sensors to have their authenticatorId invalidated for the
+ // specified user. This happens when enrollments have been added on devices with multiple
+ // biometric sensors.
+ void invalidateAuthenticatorIds(int userId, int fromSensorId, IInvalidationCallback callback);
+
// Get a list of AuthenticatorIDs for authenticators which have enrolled templates and meet
// the requirements for integrating with Keystore. The AuthenticatorID are known in Keystore
// land as SIDs, and are used during key generation.
diff --git a/core/java/android/hardware/biometrics/IInvalidationCallback.aidl b/core/java/android/hardware/biometrics/IInvalidationCallback.aidl
new file mode 100644
index 0000000..24f7d9d
--- /dev/null
+++ b/core/java/android/hardware/biometrics/IInvalidationCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.biometrics;
+
+/**
+ * Notifies the caller for BiometricManager#invalidateAuthenticatorIds status updates. See
+ * InvalidationRequesterClient for more info.
+ * @hide
+ */
+interface IInvalidationCallback {
+ // Notifies the caller when all authenticatorId(s) have been invalidated.
+ void onCompleted();
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 468157a..3b19f12 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -17,6 +17,7 @@
import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
+import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.face.IFaceServiceReceiver;
import android.hardware.face.Face;
@@ -104,6 +105,9 @@
// Return the LockoutTracker status for the specified user
int getLockoutModeForUser(int sensorId, int userId);
+ // Requests for the specified sensor+userId's authenticatorId to be invalidated
+ void invalidateAuthenticatorId(int sensorId, int userId, IInvalidationCallback callback);
+
// Gets the authenticator ID for face
long getAuthenticatorId(int sensorId, int callingUserId);
diff --git a/core/java/android/hardware/face/OWNERS b/core/java/android/hardware/face/OWNERS
index 33527f8..be10df1 100644
--- a/core/java/android/hardware/face/OWNERS
+++ b/core/java/android/hardware/face/OWNERS
@@ -1,3 +1,7 @@
# Bug component: 879035
+curtislb@google.com
+ilyamaty@google.com
jaggies@google.com
+joshmccloskey@google.com
+kchyn@google.com
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index ac026c7..74c5b58 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -17,6 +17,7 @@
import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
+import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.fingerprint.IFingerprintClientActiveCallback;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
@@ -116,6 +117,9 @@
// Return the LockoutTracker status for the specified user
int getLockoutModeForUser(int sensorId, int userId);
+ // Requests for the specified sensor+userId's authenticatorId to be invalidated
+ void invalidateAuthenticatorId(int sensorId, int userId, IInvalidationCallback callback);
+
// Gets the authenticator ID for fingerprint
long getAuthenticatorId(int sensorId, int callingUserId);
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 1a37fb9..286cdf9 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -171,6 +171,7 @@
NET_CAPABILITY_PARTIAL_CONNECTIVITY,
NET_CAPABILITY_TEMPORARILY_NOT_METERED,
NET_CAPABILITY_OEM_PRIVATE,
+ NET_CAPABILITY_VEHICLE_INTERNAL,
})
public @interface NetCapability { }
@@ -357,8 +358,17 @@
@SystemApi
public static final int NET_CAPABILITY_OEM_PRIVATE = 26;
+ /**
+ * Indicates this is an internal vehicle network, meant to communicate with other
+ * automotive systems.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int NET_CAPABILITY_VEHICLE_INTERNAL = 27;
+
private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
- private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_OEM_PRIVATE;
+ private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_VEHICLE_INTERNAL;
/**
* Network capabilities that are expected to be mutable, i.e., can change while a particular
@@ -401,15 +411,16 @@
*/
@VisibleForTesting
/* package */ static final long RESTRICTED_CAPABILITIES =
- (1 << NET_CAPABILITY_CBS) |
- (1 << NET_CAPABILITY_DUN) |
- (1 << NET_CAPABILITY_EIMS) |
- (1 << NET_CAPABILITY_FOTA) |
- (1 << NET_CAPABILITY_IA) |
- (1 << NET_CAPABILITY_IMS) |
- (1 << NET_CAPABILITY_RCS) |
- (1 << NET_CAPABILITY_XCAP) |
- (1 << NET_CAPABILITY_MCX);
+ (1 << NET_CAPABILITY_CBS)
+ | (1 << NET_CAPABILITY_DUN)
+ | (1 << NET_CAPABILITY_EIMS)
+ | (1 << NET_CAPABILITY_FOTA)
+ | (1 << NET_CAPABILITY_IA)
+ | (1 << NET_CAPABILITY_IMS)
+ | (1 << NET_CAPABILITY_MCX)
+ | (1 << NET_CAPABILITY_RCS)
+ | (1 << NET_CAPABILITY_VEHICLE_INTERNAL)
+ | (1 << NET_CAPABILITY_XCAP);
/**
* Capabilities that force network to be restricted.
@@ -1939,6 +1950,7 @@
case NET_CAPABILITY_PARTIAL_CONNECTIVITY: return "PARTIAL_CONNECTIVITY";
case NET_CAPABILITY_TEMPORARILY_NOT_METERED: return "TEMPORARILY_NOT_METERED";
case NET_CAPABILITY_OEM_PRIVATE: return "OEM_PRIVATE";
+ case NET_CAPABILITY_VEHICLE_INTERNAL: return "NET_CAPABILITY_VEHICLE_INTERNAL";
default: return Integer.toString(capability);
}
}
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 36348b3..8bfbad6 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -442,6 +442,24 @@
}
/**
+ * Check that networking is blocked for the given uid.
+ *
+ * @param uid The target uid.
+ * @param meteredNetwork True if the network is metered.
+ * @return true if networking is blocked for the given uid according to current networking
+ * policies.
+ *
+ * @hide
+ */
+ public boolean isUidNetworkingBlocked(int uid, boolean meteredNetwork) {
+ try {
+ return mService.isUidNetworkingBlocked(uid, meteredNetwork);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Get multipath preference for the given network.
*/
public int getMultipathPreference(Network network) {
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index 6209718..66b99b9 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -40,6 +40,18 @@
*/
public class NetworkRequest implements Parcelable {
/**
+ * The first requestId value that will be allocated.
+ * @hide only used by ConnectivityService.
+ */
+ public static final int FIRST_REQUEST_ID = 1;
+
+ /**
+ * The requestId value that represents the absence of a request.
+ * @hide only used by ConnectivityService.
+ */
+ public static final int REQUEST_ID_NONE = -1;
+
+ /**
* The {@link NetworkCapabilities} that define this request.
* @hide
*/
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index bf8ac6e..ba29a15a 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -39,6 +39,11 @@
POWER_COMPONENT_USAGE,
POWER_COMPONENT_CPU,
POWER_COMPONENT_BLUETOOTH,
+ POWER_COMPONENT_CAMERA,
+ POWER_COMPONENT_AUDIO,
+ POWER_COMPONENT_VIDEO,
+ POWER_COMPONENT_FLASHLIGHT,
+ POWER_COMPONENT_SYSTEM_SERVICES,
})
@Retention(RetentionPolicy.SOURCE)
public static @interface PowerComponent {
@@ -47,8 +52,13 @@
public static final int POWER_COMPONENT_USAGE = 0;
public static final int POWER_COMPONENT_CPU = 1;
public static final int POWER_COMPONENT_BLUETOOTH = 2;
+ public static final int POWER_COMPONENT_CAMERA = 3;
+ public static final int POWER_COMPONENT_AUDIO = 4;
+ public static final int POWER_COMPONENT_VIDEO = 5;
+ public static final int POWER_COMPONENT_FLASHLIGHT = 6;
+ public static final int POWER_COMPONENT_SYSTEM_SERVICES = 7;
- public static final int POWER_COMPONENT_COUNT = 3;
+ public static final int POWER_COMPONENT_COUNT = 8;
public static final int FIRST_CUSTOM_POWER_COMPONENT_ID = 1000;
public static final int LAST_CUSTOM_POWER_COMPONENT_ID = 9999;
@@ -75,6 +85,8 @@
TIME_COMPONENT_CPU,
TIME_COMPONENT_CPU_FOREGROUND,
TIME_COMPONENT_BLUETOOTH,
+ TIME_COMPONENT_CAMERA,
+ TIME_COMPONENT_FLASHLIGHT,
})
@Retention(RetentionPolicy.SOURCE)
public static @interface TimeComponent {
@@ -84,8 +96,12 @@
public static final int TIME_COMPONENT_CPU = 1;
public static final int TIME_COMPONENT_CPU_FOREGROUND = 2;
public static final int TIME_COMPONENT_BLUETOOTH = 3;
+ public static final int TIME_COMPONENT_CAMERA = 4;
+ public static final int TIME_COMPONENT_AUDIO = 5;
+ public static final int TIME_COMPONENT_VIDEO = 6;
+ public static final int TIME_COMPONENT_FLASHLIGHT = 7;
- public static final int TIME_COMPONENT_COUNT = 4;
+ public static final int TIME_COMPONENT_COUNT = 8;
public static final int FIRST_CUSTOM_TIME_COMPONENT_ID = 1000;
public static final int LAST_CUSTOM_TIME_COMPONENT_ID = 9999;
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index e3ad4e6..2559a33 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -23,6 +23,10 @@
per-file BatteryUsageStats* = file:/BATTERY_STATS_OWNERS
per-file PowerComponents.java = file:/BATTERY_STATS_OWNERS
+# Multiuser
+per-file IUser* = file:/MULTIUSER_OWNERS
+per-file User* = file:/MULTIUSER_OWNERS
+
# Binder
per-file BadParcelableException.java = file:platform/frameworks/native:/libs/binder/OWNERS
per-file Binder.java = file:platform/frameworks/native:/libs/binder/OWNERS
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 8048b9d..39e3e14 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -31,9 +31,12 @@
import libcore.io.IoUtils;
+import java.io.BufferedReader;
import java.io.FileDescriptor;
+import java.io.FileReader;
import java.io.IOException;
import java.util.Map;
+import java.util.StringTokenizer;
import java.util.concurrent.TimeoutException;
/**
@@ -1430,4 +1433,38 @@
}
private static native int nativePidFdOpen(int pid, int flags) throws ErrnoException;
+
+ /**
+ * Checks if a process corresponding to a specific pid owns any file locks.
+ * @param pid The process ID for which we want to know the existence of file locks.
+ * @return true If the process holds any file locks, false otherwise.
+ * @throws IOException if /proc/locks can't be accessed.
+ *
+ * @hide
+ */
+ public static boolean hasFileLocks(int pid) throws IOException {
+ BufferedReader br = null;
+
+ try {
+ br = new BufferedReader(new FileReader("/proc/locks"));
+ String line;
+
+ while ((line = br.readLine()) != null) {
+ StringTokenizer st = new StringTokenizer(line);
+
+ for (int i = 0; i < 5 && st.hasMoreTokens(); i++) {
+ String str = st.nextToken();
+ if (i == 4 && Integer.parseInt(str) == pid) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ } finally {
+ if (br != null) {
+ br.close();
+ }
+ }
+ }
}
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index b57418d..c0b2ada 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -1071,6 +1071,7 @@
PRIMITIVE_SLOW_RISE,
PRIMITIVE_QUICK_FALL,
PRIMITIVE_TICK,
+ PRIMITIVE_LOW_TICK,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Primitive {}
@@ -1116,6 +1117,12 @@
*/
// Internally this maps to the HAL constant CompositePrimitive::LIGHT_TICK
public static final int PRIMITIVE_TICK = 7;
+ /**
+ * This very short low frequency effect should produce a light crisp sensation
+ * intended to be used repetitively for dynamic feedback.
+ */
+ // Internally this maps to the HAL constant CompositePrimitive::LOW_TICK
+ public static final int PRIMITIVE_LOW_TICK = 8;
private ArrayList<PrimitiveEffect> mEffects = new ArrayList<>();
@@ -1194,7 +1201,7 @@
*
*/
static int checkPrimitive(int primitiveId) {
- Preconditions.checkArgumentInRange(primitiveId, PRIMITIVE_NOOP, PRIMITIVE_TICK,
+ Preconditions.checkArgumentInRange(primitiveId, PRIMITIVE_NOOP, PRIMITIVE_LOW_TICK,
"primitiveId");
return primitiveId;
}
@@ -1223,6 +1230,8 @@
return "PRIMITIVE_QUICK_FALL";
case PRIMITIVE_TICK:
return "PRIMITIVE_TICK";
+ case PRIMITIVE_LOW_TICK:
+ return "PRIMITIVE_LOW_TICK";
default:
return Integer.toString(id);
}
diff --git a/core/java/android/os/incremental/IIncrementalService.aidl b/core/java/android/os/incremental/IIncrementalService.aidl
index ca92ad5..7db5a80 100644
--- a/core/java/android/os/incremental/IIncrementalService.aidl
+++ b/core/java/android/os/incremental/IIncrementalService.aidl
@@ -21,6 +21,7 @@
import android.os.incremental.IncrementalNewFileParams;
import android.os.incremental.IStorageLoadingProgressListener;
import android.os.incremental.IStorageHealthListener;
+import android.os.incremental.PerUidReadTimeouts;
import android.os.incremental.StorageHealthCheckParams;
/** @hide */
@@ -40,7 +41,8 @@
int createStorage(in @utf8InCpp String path, in DataLoaderParamsParcel params, int createMode,
in IDataLoaderStatusListener statusListener,
in StorageHealthCheckParams healthCheckParams,
- in IStorageHealthListener healthListener);
+ in IStorageHealthListener healthListener,
+ in PerUidReadTimeouts[] perUidReadTimeouts);
int createLinkedStorage(in @utf8InCpp String path, int otherStorageId, int createMode);
/**
@@ -123,7 +125,7 @@
/**
* Permanently disable readlogs reporting for a storage given its ID.
*/
- void disableReadLogs(int storageId);
+ void disallowReadLogs(int storageId);
/**
* Setting up native library directories and extract native libs onto a storage if needed.
diff --git a/core/java/android/os/incremental/IncrementalFileStorages.java b/core/java/android/os/incremental/IncrementalFileStorages.java
index 284c2df..59292baa 100644
--- a/core/java/android/os/incremental/IncrementalFileStorages.java
+++ b/core/java/android/os/incremental/IncrementalFileStorages.java
@@ -69,7 +69,8 @@
@Nullable IDataLoaderStatusListener statusListener,
@Nullable StorageHealthCheckParams healthCheckParams,
@Nullable IStorageHealthListener healthListener,
- List<InstallationFileParcel> addedFiles) throws IOException {
+ @NonNull List<InstallationFileParcel> addedFiles,
+ @NonNull PerUidReadTimeouts[] perUidReadTimeouts) throws IOException {
// TODO(b/136132412): validity check if session should not be incremental
IncrementalManager incrementalManager = (IncrementalManager) context.getSystemService(
Context.INCREMENTAL_SERVICE);
@@ -80,7 +81,7 @@
final IncrementalFileStorages result = new IncrementalFileStorages(stageDir,
incrementalManager, dataLoaderParams, statusListener, healthCheckParams,
- healthListener);
+ healthListener, perUidReadTimeouts);
for (InstallationFileParcel file : addedFiles) {
if (file.location == LOCATION_DATA_APP) {
try {
@@ -105,7 +106,8 @@
@NonNull DataLoaderParams dataLoaderParams,
@Nullable IDataLoaderStatusListener statusListener,
@Nullable StorageHealthCheckParams healthCheckParams,
- @Nullable IStorageHealthListener healthListener) throws IOException {
+ @Nullable IStorageHealthListener healthListener,
+ @NonNull PerUidReadTimeouts[] perUidReadTimeouts) throws IOException {
try {
mStageDir = stageDir;
mIncrementalManager = incrementalManager;
@@ -124,7 +126,7 @@
mDefaultStorage = mIncrementalManager.createStorage(stageDir.getAbsolutePath(),
dataLoaderParams, IncrementalManager.CREATE_MODE_CREATE
| IncrementalManager.CREATE_MODE_TEMPORARY_BIND, false,
- statusListener, healthCheckParams, healthListener);
+ statusListener, healthCheckParams, healthListener, perUidReadTimeouts);
if (mDefaultStorage == null) {
throw new IOException(
"Couldn't create incremental storage at " + stageDir);
@@ -163,8 +165,8 @@
/**
* Permanently disables readlogs.
*/
- public void disableReadLogs() {
- mDefaultStorage.disableReadLogs();
+ public void disallowReadLogs() {
+ mDefaultStorage.disallowReadLogs();
}
/**
diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java
index fb47ef0..4b93270 100644
--- a/core/java/android/os/incremental/IncrementalManager.java
+++ b/core/java/android/os/incremental/IncrementalManager.java
@@ -40,6 +40,7 @@
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Objects;
/**
* Provides operations to open or create an IncrementalStorage, using IIncrementalService
@@ -104,10 +105,14 @@
boolean autoStartDataLoader,
@Nullable IDataLoaderStatusListener statusListener,
@Nullable StorageHealthCheckParams healthCheckParams,
- @Nullable IStorageHealthListener healthListener) {
+ @Nullable IStorageHealthListener healthListener,
+ @NonNull PerUidReadTimeouts[] perUidReadTimeouts) {
+ Objects.requireNonNull(path);
+ Objects.requireNonNull(params);
+ Objects.requireNonNull(perUidReadTimeouts);
try {
final int id = mService.createStorage(path, params.getData(), createMode,
- statusListener, healthCheckParams, healthListener);
+ statusListener, healthCheckParams, healthListener, perUidReadTimeouts);
if (id < 0) {
return null;
}
diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java
index b913faf..5b688bb 100644
--- a/core/java/android/os/incremental/IncrementalStorage.java
+++ b/core/java/android/os/incremental/IncrementalStorage.java
@@ -432,9 +432,9 @@
/**
* Permanently disable readlogs collection.
*/
- public void disableReadLogs() {
+ public void disallowReadLogs() {
try {
- mService.disableReadLogs(mId);
+ mService.disallowReadLogs(mId);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
diff --git a/core/java/android/os/incremental/PerUidReadTimeouts.aidl b/core/java/android/os/incremental/PerUidReadTimeouts.aidl
new file mode 100644
index 0000000..84f30a6
--- /dev/null
+++ b/core/java/android/os/incremental/PerUidReadTimeouts.aidl
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.incremental;
+
+/**
+ * Max value is ~1hr = 3600s = 3600000ms = 3600000000us
+ * @hide
+ */
+parcelable PerUidReadTimeouts {
+ /** UID to apply these timeouts to */
+ int uid;
+
+ /**
+ * Min time to read any block. Note that this doesn't apply to reads
+ * which are satisfied from the page cache.
+ */
+ long minTimeUs;
+
+ /**
+ * Min time to satisfy a pending read. Must be >= min_time_us. Any
+ * pending read which is filled before this time will be delayed so
+ * that the total read time >= this value.
+ */
+ long minPendingTimeUs;
+
+ /**
+ * Max time to satisfy a pending read before the read times out.
+ * Must be >= min_pending_time_us
+ */
+ long maxPendingTimeUs;
+}
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 8ac1fc1..b5abe2a 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -163,7 +163,7 @@
mMaxFileSize = in.readLong();
mOwner = in.readParcelable(null);
if (in.readInt() != 0) {
- mUuid = StorageManager.convert(in.readString());
+ mUuid = StorageManager.convert(in.readString8());
} else {
mUuid = null;
}
diff --git a/core/java/android/permission/PermGroupUsage.java b/core/java/android/permission/PermGroupUsage.java
index 3bee401..7335e00 100644
--- a/core/java/android/permission/PermGroupUsage.java
+++ b/core/java/android/permission/PermGroupUsage.java
@@ -30,17 +30,19 @@
private final String mPackageName;
private final int mUid;
+ private final long mLastAccess;
private final String mPermGroupName;
private final boolean mIsActive;
private final boolean mIsPhoneCall;
private final CharSequence mAttribution;
PermGroupUsage(@NonNull String packageName, int uid,
- @NonNull String permGroupName, boolean isActive, boolean isPhoneCall,
+ @NonNull String permGroupName, long lastAccess, boolean isActive, boolean isPhoneCall,
@Nullable CharSequence attribution) {
this.mPackageName = packageName;
this.mUid = uid;
this.mPermGroupName = permGroupName;
+ this.mLastAccess = lastAccess;
this.mIsActive = isActive;
this.mIsPhoneCall = isPhoneCall;
this.mAttribution = attribution;
@@ -58,6 +60,10 @@
return mPermGroupName;
}
+ public long getLastAccess() {
+ return mLastAccess;
+ }
+
public boolean isActive() {
return mIsActive;
}
diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java
index 4868827..00ba867 100644
--- a/core/java/android/permission/PermissionUsageHelper.java
+++ b/core/java/android/permission/PermissionUsageHelper.java
@@ -187,7 +187,7 @@
for (int usageNum = 0; usageNum < numUsages; usageNum++) {
OpUsage usage = rawUsages.get(permGroup).get(usageNum);
usages.add(new PermGroupUsage(usage.packageName, usage.uid, permGroup,
- usage.isRunning, isPhone,
+ usage.lastAccessTime, usage.isRunning, isPhone,
packagesWithAttributionLabels.get(usage.toPackageAttr())));
}
}
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index d1aa489..541daa3 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -54,6 +54,7 @@
import android.text.TextUtils;
import android.util.Log;
+import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -96,6 +97,10 @@
*/
public static final String SHADOW_AUTHORITY = "call_log_shadow";
+ /** @hide */
+ public static final Uri SHADOW_CALL_COMPOSER_PICTURE_URI = CALL_COMPOSER_PICTURE_URI.buildUpon()
+ .authority(SHADOW_AUTHORITY).build();
+
/**
* Describes an error encountered while storing a call composer picture in the call log.
* @hide
@@ -154,6 +159,29 @@
public @CallComposerLoggingError int getErrorCode() {
return mErrorCode;
}
+
+ @Override
+ public String toString() {
+ String errorString;
+ switch (mErrorCode) {
+ case ERROR_UNKNOWN:
+ errorString = "UNKNOWN";
+ break;
+ case ERROR_REMOTE_END_CLOSED:
+ errorString = "REMOTE_END_CLOSED";
+ break;
+ case ERROR_STORAGE_FULL:
+ errorString = "STORAGE_FULL";
+ break;
+ case ERROR_INPUT_CLOSED:
+ errorString = "INPUT_CLOSED";
+ break;
+ default:
+ errorString = "[[" + mErrorCode + "]]";
+ break;
+ }
+ return "CallComposerLoggingException: " + errorString;
+ }
}
/**
@@ -179,7 +207,10 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.WRITE_CALL_LOG)
+ @RequiresPermission(allOf = {
+ Manifest.permission.WRITE_CALL_LOG,
+ Manifest.permission.INTERACT_ACROSS_USERS
+ })
public static void storeCallComposerPictureAsUser(@NonNull Context context,
@Nullable UserHandle user,
@NonNull InputStream input,
@@ -191,69 +222,164 @@
Objects.requireNonNull(callback);
executor.execute(() -> {
- Uri pictureFileUri;
- Uri pictureInsertionUri = context.getSystemService(UserManager.class)
- .isUserUnlocked() ? CALL_COMPOSER_PICTURE_URI
- : CALL_COMPOSER_PICTURE_URI.buildUpon().authority(SHADOW_AUTHORITY).build();
- try {
- // ContentResolver#insert says that the second argument is nullable. It is in fact
- // not nullable.
- ContentValues cv = new ContentValues();
- pictureFileUri = context.getContentResolver().insert(pictureInsertionUri, cv);
- } catch (ParcelableException e) {
- // Most likely an IOException. We don't have a good way of distinguishing them so
- // just return an unknown error.
- sendCallComposerError(callback, CallComposerLoggingException.ERROR_UNKNOWN);
- return;
+ ByteArrayOutputStream tmpOut = new ByteArrayOutputStream();
+
+ // Read the entire input into memory first in case we have to write multiple times and
+ // the input isn't resettable.
+ byte[] buffer = new byte[1024];
+ int bytesRead;
+ while (true) {
+ try {
+ bytesRead = input.read(buffer);
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "IOException while reading call composer pic from input: "
+ + e);
+ callback.onError(new CallComposerLoggingException(
+ CallComposerLoggingException.ERROR_INPUT_CLOSED));
+ return;
+ }
+ if (bytesRead < 0) {
+ break;
+ }
+ tmpOut.write(buffer, 0, bytesRead);
}
- if (pictureFileUri == null) {
- // If the call log provider returns null, it means that there's not enough space
- // left to store the maximum-sized call composer image.
- sendCallComposerError(callback, CallComposerLoggingException.ERROR_STORAGE_FULL);
+ byte[] picData = tmpOut.toByteArray();
+
+ UserManager userManager = context.getSystemService(UserManager.class);
+ // Nasty casework for the shadow calllog begins...
+ // First see if we're just inserting for one user. If so, insert into the shadow
+ // based on whether that user is unlocked.
+ if (user != null) {
+ Uri baseUri = userManager.isUserUnlocked(user) ? CALL_COMPOSER_PICTURE_URI
+ : SHADOW_CALL_COMPOSER_PICTURE_URI;
+ Uri pictureInsertionUri = ContentProvider.maybeAddUserId(baseUri,
+ user.getIdentifier());
+ Log.i(LOG_TAG, "Inserting call composer for single user at "
+ + pictureInsertionUri);
+
+ try {
+ Uri result = storeCallComposerPictureAtUri(
+ context, pictureInsertionUri, false, picData);
+ callback.onResult(result);
+ } catch (CallComposerLoggingException e) {
+ callback.onError(e);
+ }
return;
}
- boolean wroteSuccessfully = false;
- try (ParcelFileDescriptor pfd =
- context.getContentResolver().openFileDescriptor(pictureFileUri, "w")) {
- FileOutputStream output = new FileOutputStream(pfd.getFileDescriptor());
- byte[] buffer = new byte[1024];
- int bytesRead;
- while (true) {
+ // Next, see if the system user is locked. If so, only insert to the system shadow
+ if (!userManager.isUserUnlocked(UserHandle.SYSTEM)) {
+ Uri pictureInsertionUri = ContentProvider.maybeAddUserId(
+ SHADOW_CALL_COMPOSER_PICTURE_URI,
+ UserHandle.SYSTEM.getIdentifier());
+ Log.i(LOG_TAG, "Inserting call composer for all users, but system locked at "
+ + pictureInsertionUri);
+ try {
+ Uri result =
+ storeCallComposerPictureAtUri(context, pictureInsertionUri,
+ true, picData);
+ callback.onResult(result);
+ } catch (CallComposerLoggingException e) {
+ callback.onError(e);
+ }
+ return;
+ }
+
+ // If we're inserting to all users and the system user is unlocked, then insert to all
+ // running users. Non running/still locked users will copy from the system when they
+ // start.
+ // First, insert to the system calllog to get the basename to use for the rest of the
+ // users.
+ Uri systemPictureInsertionUri = ContentProvider.maybeAddUserId(
+ CALL_COMPOSER_PICTURE_URI,
+ UserHandle.SYSTEM.getIdentifier());
+ Uri systemInsertedPicture;
+ try {
+ systemInsertedPicture =
+ storeCallComposerPictureAtUri(context, systemPictureInsertionUri,
+ true, picData);
+ Log.i(LOG_TAG, "Inserting call composer for all users, succeeded with system,"
+ + " result is " + systemInsertedPicture);
+ } catch (CallComposerLoggingException e) {
+ callback.onError(e);
+ return;
+ }
+
+ // Next, insert into all users that have call log access AND are running AND are
+ // decrypted.
+ Uri strippedInsertionUri = ContentProvider.getUriWithoutUserId(systemInsertedPicture);
+ for (UserInfo u : userManager.getAliveUsers()) {
+ UserHandle userHandle = u.getUserHandle();
+ if (userHandle.isSystem()) {
+ // Already written.
+ continue;
+ }
+
+ if (!Calls.shouldHaveSharedCallLogEntries(
+ context, userManager, userHandle.getIdentifier())) {
+ // Shouldn't have calllog entries.
+ continue;
+ }
+
+ if (userManager.isUserRunning(userHandle)
+ && userManager.isUserUnlocked(userHandle)) {
+ Uri insertionUri = ContentProvider.maybeAddUserId(strippedInsertionUri,
+ userHandle.getIdentifier());
+ Log.i(LOG_TAG, "Inserting call composer for all users, now on user "
+ + userHandle + " inserting at " + insertionUri);
try {
- bytesRead = input.read(buffer);
- } catch (IOException e) {
- sendCallComposerError(callback,
- CallComposerLoggingException.ERROR_INPUT_CLOSED);
- throw e;
- }
- if (bytesRead < 0) {
- break;
- }
- try {
- output.write(buffer, 0, bytesRead);
- } catch (IOException e) {
- sendCallComposerError(callback,
- CallComposerLoggingException.ERROR_REMOTE_END_CLOSED);
- throw e;
+ storeCallComposerPictureAtUri(context, insertionUri, false, picData);
+ } catch (CallComposerLoggingException e) {
+ Log.e(LOG_TAG, "Error writing for user " + userHandle.getIdentifier()
+ + ": " + e);
+ // If one or more users failed but the system user succeeded, don't return
+ // an error -- the image is still around somewhere, and we'll be able to
+ // find it in the system user's call log if needed.
}
}
- wroteSuccessfully = true;
- } catch (FileNotFoundException e) {
- callback.onError(new CallComposerLoggingException(
- CallComposerLoggingException.ERROR_UNKNOWN));
- } catch (IOException e) {
- Log.e(LOG_TAG, "IOException while writing call composer pic to call log: "
- + e);
}
+ callback.onResult(strippedInsertionUri);
+ });
+ }
- if (wroteSuccessfully) {
- callback.onResult(pictureFileUri);
- } else {
+ private static Uri storeCallComposerPictureAtUri(
+ Context context, Uri insertionUri,
+ boolean forAllUsers, byte[] picData) throws CallComposerLoggingException {
+ Uri pictureFileUri;
+ try {
+ ContentValues cv = new ContentValues();
+ cv.put(Calls.ADD_FOR_ALL_USERS, forAllUsers ? 1 : 0);
+ pictureFileUri = context.getContentResolver().insert(insertionUri, cv);
+ } catch (ParcelableException e) {
+ // Most likely an IOException. We don't have a good way of distinguishing them so
+ // just return an unknown error.
+ throw new CallComposerLoggingException(CallComposerLoggingException.ERROR_UNKNOWN);
+ }
+ if (pictureFileUri == null) {
+ // If the call log provider returns null, it means that there's not enough space
+ // left to store the maximum-sized call composer image.
+ throw new CallComposerLoggingException(CallComposerLoggingException.ERROR_STORAGE_FULL);
+ }
+
+ try (ParcelFileDescriptor pfd =
+ context.getContentResolver().openFileDescriptor(pictureFileUri, "w")) {
+ FileOutputStream output = new FileOutputStream(pfd.getFileDescriptor());
+ try {
+ output.write(picData);
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Got IOException writing to remote end: " + e);
// Clean up our mess if we didn't successfully write the file.
context.getContentResolver().delete(pictureFileUri, null);
+ throw new CallComposerLoggingException(
+ CallComposerLoggingException.ERROR_REMOTE_END_CLOSED);
}
- });
+ } catch (FileNotFoundException e) {
+ throw new CallComposerLoggingException(CallComposerLoggingException.ERROR_UNKNOWN);
+ } catch (IOException e) {
+ // Ignore, this is only thrown upon closing.
+ Log.e(LOG_TAG, "Got IOException closing remote descriptor: " + e);
+ }
+ return pictureFileUri;
}
// Only call on the correct executor.
diff --git a/core/java/android/service/translation/ITranslationCallback.aidl b/core/java/android/service/translation/ITranslationCallback.aidl
new file mode 100644
index 0000000..333cb57
--- /dev/null
+++ b/core/java/android/service/translation/ITranslationCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.translation;
+
+import android.view.translation.TranslationResponse;
+
+/**
+ * Interface to receive the result of a {@code TranslationRequest}.
+ *
+ * @hide
+ */
+oneway interface ITranslationCallback {
+ void onTranslationComplete(in TranslationResponse translationResponse);
+ void onError();
+}
diff --git a/core/java/android/service/translation/ITranslationService.aidl b/core/java/android/service/translation/ITranslationService.aidl
new file mode 100644
index 0000000..6d6f278
--- /dev/null
+++ b/core/java/android/service/translation/ITranslationService.aidl
@@ -0,0 +1,38 @@
+/*
+ * 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.service.translation;
+
+import android.service.translation.TranslationRequest;
+import android.service.translation.ITranslationCallback;
+import android.view.translation.TranslationSpec;
+import com.android.internal.os.IResultReceiver;
+
+/**
+ * System-wide on-device translation service.
+ *
+ * <p>Services requests to translate text between different languages. The primary use case for this
+ * service is automatic translation of text and web views, when the auto Translate feature is
+ * enabled.
+ *
+ * @hide
+ */
+oneway interface ITranslationService {
+ void onConnected();
+ void onDisconnected();
+ void onCreateTranslationSession(in TranslationSpec sourceSpec, in TranslationSpec destSpec,
+ int sessionId, in IResultReceiver receiver);
+}
diff --git a/core/java/android/service/translation/OWNERS b/core/java/android/service/translation/OWNERS
new file mode 100644
index 0000000..a1e663a
--- /dev/null
+++ b/core/java/android/service/translation/OWNERS
@@ -0,0 +1,8 @@
+# Bug component: 994311
+
+adamhe@google.com
+augale@google.com
+joannechung@google.com
+lpeter@google.com
+svetoslavganov@google.com
+tymtsai@google.com
diff --git a/core/java/android/service/translation/OnTranslationResultCallbackWrapper.java b/core/java/android/service/translation/OnTranslationResultCallbackWrapper.java
new file mode 100644
index 0000000..345c69c
--- /dev/null
+++ b/core/java/android/service/translation/OnTranslationResultCallbackWrapper.java
@@ -0,0 +1,92 @@
+/*
+ * 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.service.translation;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.DeadObjectException;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.translation.TranslationResponse;
+
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Callback to receive the {@link TranslationResponse} on successful translation.
+ *
+ * @hide
+ */
+final class OnTranslationResultCallbackWrapper implements
+ TranslationService.OnTranslationResultCallback {
+
+ private static final String TAG = "OnTranslationResultCallback";
+
+ private final @NonNull ITranslationCallback mCallback;
+
+ private AtomicBoolean mCalled;
+
+ /**
+ * @hide
+ */
+ public OnTranslationResultCallbackWrapper(@NonNull ITranslationCallback callback) {
+ mCallback = Objects.requireNonNull(callback);
+ mCalled = new AtomicBoolean();
+ }
+
+ @Override
+ public void onTranslationSuccess(@Nullable TranslationResponse response) {
+ assertNotCalled();
+ if (mCalled.getAndSet(true)) {
+ throw new IllegalStateException("Already called");
+ }
+
+ try {
+ mCallback.onTranslationComplete(response);
+ } catch (RemoteException e) {
+ if (e instanceof DeadObjectException) {
+ Log.w(TAG, "Process is dead, ignore.");
+ return;
+ }
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
+ public void onError() {
+ assertNotCalled();
+ if (mCalled.getAndSet(true)) {
+ throw new IllegalStateException("Already called");
+ }
+
+ try {
+ mCallback.onError();
+ } catch (RemoteException e) {
+ if (e instanceof DeadObjectException) {
+ Log.w(TAG, "Process is dead, ignore.");
+ return;
+ }
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ private void assertNotCalled() {
+ if (mCalled.get()) {
+ throw new IllegalStateException("Already called");
+ }
+ }
+}
diff --git a/core/java/android/service/translation/TranslationRequest.aidl b/core/java/android/service/translation/TranslationRequest.aidl
new file mode 100644
index 0000000..9a2d415
--- /dev/null
+++ b/core/java/android/service/translation/TranslationRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.service.translation;
+
+parcelable TranslationRequest;
diff --git a/core/java/android/service/translation/TranslationRequest.java b/core/java/android/service/translation/TranslationRequest.java
new file mode 100644
index 0000000..b8afd70
--- /dev/null
+++ b/core/java/android/service/translation/TranslationRequest.java
@@ -0,0 +1,281 @@
+/*
+ * 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.service.translation;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.translation.TranslationSpec;
+
+import com.android.internal.util.DataClass;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Internal translation request sent to the {@link android.service.translation.TranslationService}
+ * which contains the text to be translated.
+ *
+ * @hide
+ */
+@SystemApi
+@DataClass(genConstructor = true, genBuilder = true, genToString = true)
+public final class TranslationRequest implements Parcelable {
+
+ private final int mRequestId;
+ @NonNull
+ private final TranslationSpec mSourceSpec;
+ @NonNull
+ private final TranslationSpec mDestSpec;
+ @NonNull
+ private final List<android.view.translation.TranslationRequest> mTranslationRequests;
+
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/translation/TranslationRequest.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @DataClass.Generated.Member
+ public TranslationRequest(
+ int requestId,
+ @NonNull TranslationSpec sourceSpec,
+ @NonNull TranslationSpec destSpec,
+ @NonNull List<android.view.translation.TranslationRequest> translationRequests) {
+ this.mRequestId = requestId;
+ this.mSourceSpec = sourceSpec;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mSourceSpec);
+ this.mDestSpec = destSpec;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mDestSpec);
+ this.mTranslationRequests = translationRequests;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mTranslationRequests);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public int getRequestId() {
+ return mRequestId;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull TranslationSpec getSourceSpec() {
+ return mSourceSpec;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull TranslationSpec getDestSpec() {
+ return mDestSpec;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull List<android.view.translation.TranslationRequest> getTranslationRequests() {
+ return mTranslationRequests;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "TranslationRequest { " +
+ "requestId = " + mRequestId + ", " +
+ "sourceSpec = " + mSourceSpec + ", " +
+ "destSpec = " + mDestSpec + ", " +
+ "translationRequests = " + mTranslationRequests +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeInt(mRequestId);
+ dest.writeTypedObject(mSourceSpec, flags);
+ dest.writeTypedObject(mDestSpec, flags);
+ dest.writeParcelableList(mTranslationRequests, flags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ TranslationRequest(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ int requestId = in.readInt();
+ TranslationSpec sourceSpec = (TranslationSpec) in.readTypedObject(TranslationSpec.CREATOR);
+ TranslationSpec destSpec = (TranslationSpec) in.readTypedObject(TranslationSpec.CREATOR);
+ List<android.view.translation.TranslationRequest> translationRequests = new ArrayList<>();
+ in.readParcelableList(translationRequests, android.view.translation.TranslationRequest.class.getClassLoader());
+
+ this.mRequestId = requestId;
+ this.mSourceSpec = sourceSpec;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mSourceSpec);
+ this.mDestSpec = destSpec;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mDestSpec);
+ this.mTranslationRequests = translationRequests;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mTranslationRequests);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<TranslationRequest> CREATOR
+ = new Parcelable.Creator<TranslationRequest>() {
+ @Override
+ public TranslationRequest[] newArray(int size) {
+ return new TranslationRequest[size];
+ }
+
+ @Override
+ public TranslationRequest createFromParcel(@NonNull Parcel in) {
+ return new TranslationRequest(in);
+ }
+ };
+
+ /**
+ * A builder for {@link TranslationRequest}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private int mRequestId;
+ private @NonNull TranslationSpec mSourceSpec;
+ private @NonNull TranslationSpec mDestSpec;
+ private @NonNull List<android.view.translation.TranslationRequest> mTranslationRequests;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder(
+ int requestId,
+ @NonNull TranslationSpec sourceSpec,
+ @NonNull TranslationSpec destSpec,
+ @NonNull List<android.view.translation.TranslationRequest> translationRequests) {
+ mRequestId = requestId;
+ mSourceSpec = sourceSpec;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mSourceSpec);
+ mDestSpec = destSpec;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mDestSpec);
+ mTranslationRequests = translationRequests;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mTranslationRequests);
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull Builder setRequestId(int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mRequestId = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull Builder setSourceSpec(@NonNull TranslationSpec value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mSourceSpec = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull Builder setDestSpec(@NonNull TranslationSpec value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mDestSpec = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull Builder setTranslationRequests(@NonNull List<android.view.translation.TranslationRequest> value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8;
+ mTranslationRequests = value;
+ return this;
+ }
+
+ /** @see #setTranslationRequests */
+ @DataClass.Generated.Member
+ public @NonNull Builder addTranslationRequests(@NonNull android.view.translation.TranslationRequest value) {
+ // You can refine this method's name by providing item's singular name, e.g.:
+ // @DataClass.PluralOf("item")) mItems = ...
+
+ if (mTranslationRequests == null) setTranslationRequests(new ArrayList<>());
+ mTranslationRequests.add(value);
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull TranslationRequest build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x10; // Mark builder used
+
+ TranslationRequest o = new TranslationRequest(
+ mRequestId,
+ mSourceSpec,
+ mDestSpec,
+ mTranslationRequests);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x10) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1609966181888L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/service/translation/TranslationRequest.java",
+ inputSignatures = "private final int mRequestId\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mSourceSpec\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mDestSpec\nprivate final @android.annotation.NonNull java.util.List<android.view.translation.TranslationRequest> mTranslationRequests\nclass TranslationRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=true, genBuilder=true, genToString=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/service/translation/TranslationService.java b/core/java/android/service/translation/TranslationService.java
new file mode 100644
index 0000000..b028807
--- /dev/null
+++ b/core/java/android/service/translation/TranslationService.java
@@ -0,0 +1,261 @@
+/*
+ * 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.service.translation;
+
+import static android.view.translation.Translator.EXTRA_SERVICE_BINDER;
+import static android.view.translation.Translator.EXTRA_SESSION_ID;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.os.BaseBundle;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.translation.ITranslationDirectManager;
+import android.view.translation.TranslationManager;
+import android.view.translation.TranslationResponse;
+import android.view.translation.TranslationSpec;
+
+import com.android.internal.os.IResultReceiver;
+import com.android.internal.util.SyncResultReceiver;
+
+/**
+ * Service for translating text.
+ * @hide
+ */
+@SystemApi
+public abstract class TranslationService extends Service {
+ private static final String TAG = "TranslationService";
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ *
+ * <p>To be supported, the service must also require the
+ * {@link android.Manifest.permission#BIND_TRANSLATION_SERVICE} permission so
+ * that other applications can not abuse it.
+ */
+ public static final String SERVICE_INTERFACE =
+ "android.service.translation.TranslationService";
+
+ /**
+ * Name under which a TranslationService component publishes information about itself.
+ *
+ * <p>This meta-data should reference an XML resource containing a
+ * <code><{@link
+ * android.R.styleable#TranslationService translation-service}></code> tag.
+ *
+ * <p>Here's an example of how to use it on {@code AndroidManifest.xml}:
+ * TODO: fill in doc example (check CCService/AFService).
+ */
+ public static final String SERVICE_META_DATA = "android.translation_service";
+
+ private Handler mHandler;
+
+ /**
+ * Binder to receive calls from system server.
+ */
+ private final ITranslationService mInterface = new ITranslationService.Stub() {
+ @Override
+ public void onConnected() {
+ mHandler.sendMessage(obtainMessage(TranslationService::onConnected,
+ TranslationService.this));
+ }
+
+ @Override
+ public void onDisconnected() {
+ mHandler.sendMessage(obtainMessage(TranslationService::onDisconnected,
+ TranslationService.this));
+ }
+
+ @Override
+ public void onCreateTranslationSession(TranslationSpec sourceSpec, TranslationSpec destSpec,
+ int sessionId, IResultReceiver receiver) throws RemoteException {
+ mHandler.sendMessage(obtainMessage(TranslationService::handleOnCreateTranslationSession,
+ TranslationService.this, sourceSpec, destSpec, sessionId, receiver));
+ }
+ };
+
+ /**
+ * Interface definition for a callback to be invoked when the translation is compleled.
+ */
+ public interface OnTranslationResultCallback {
+ /**
+ * Notifies the Android System that a translation request
+ * {@link TranslationService#onTranslationRequest(TranslationRequest, int,
+ * CancellationSignal, OnTranslationResultCallback)} was successfully fulfilled by the
+ * service.
+ *
+ * <p>This method should always be called, even if the service cannot fulfill the request
+ * (in which case it should be called with a TranslationResponse with
+ * {@link android.view.translation.TranslationResponse#TRANSLATION_STATUS_UNKNOWN_ERROR},
+ * or {@link android.view.translation.TranslationResponse
+ * #TRANSLATION_STATUS_LANGUAGE_UNAVAILABLE}).
+ *
+ * @param response translation response for the provided request infos.
+ *
+ * @throws IllegalStateException if this method was already called.
+ */
+ void onTranslationSuccess(@NonNull TranslationResponse response);
+
+ /**
+ * TODO: implement javadoc
+ */
+ void onError();
+ }
+
+ /**
+ * Binder that receives calls from the app.
+ */
+ private final ITranslationDirectManager mClientInterface =
+ new ITranslationDirectManager.Stub() {
+ // TODO: Implement cancellation signal
+ @NonNull
+ private final CancellationSignal mCancellationSignal = new CancellationSignal();
+
+ @Override
+ public void onTranslationRequest(TranslationRequest request, int sessionId,
+ ITranslationCallback callback, IResultReceiver receiver)
+ throws RemoteException {
+ // TODO(b/176464808): Currently, the API is used for both sync and async case.
+ // It may work now, but maybe two methods is more cleaner. To think how to
+ // define the APIs for these two cases.
+ final ITranslationCallback cb = callback != null
+ ? callback
+ : new ITranslationCallback.Stub() {
+ @Override
+ public void onTranslationComplete(
+ TranslationResponse translationResponse)
+ throws RemoteException {
+ receiver.send(0,
+ SyncResultReceiver.bundleFor(translationResponse));
+ }
+
+ @Override
+ public void onError() throws RemoteException {
+ //TODO: implement default error callback
+ }
+ };
+ // TODO(b/176464808): make it a private member of client
+ final OnTranslationResultCallback translationResultCallback =
+ new OnTranslationResultCallbackWrapper(cb);
+ mHandler.sendMessage(obtainMessage(TranslationService::onTranslationRequest,
+ TranslationService.this, request, sessionId, mCancellationSignal,
+ translationResultCallback));
+ }
+
+ @Override
+ public void onFinishTranslationSession(int sessionId) throws RemoteException {
+ mHandler.sendMessage(obtainMessage(
+ TranslationService::onFinishTranslationSession,
+ TranslationService.this, sessionId));
+ }
+ };
+
+ @CallSuper
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mHandler = new Handler(Looper.getMainLooper(), null, true);
+ BaseBundle.setShouldDefuse(true);
+ }
+
+ @Override
+ @Nullable
+ public final IBinder onBind(@NonNull Intent intent) {
+ if (SERVICE_INTERFACE.equals(intent.getAction())) {
+ return mInterface.asBinder();
+ }
+ Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent);
+ return null;
+ }
+
+ /**
+ * Called when the Android system connects to service.
+ *
+ * <p>You should generally do initialization here rather than in {@link #onCreate}.
+ */
+ public void onConnected() {
+ }
+
+ /**
+ * Called when the Android system disconnects from the service.
+ *
+ * <p> At this point this service may no longer be an active {@link TranslationService}.
+ * It should not make calls on {@link TranslationManager} that requires the caller to be
+ * the current service.
+ */
+ public void onDisconnected() {
+ }
+
+ /**
+ * TODO: fill in javadoc.
+ *
+ * @param sourceSpec
+ * @param destSpec
+ * @param sessionId
+ */
+ // TODO(b/176464808): the session id won't be unique cross client/server process. Need to find
+ // solution to make it's safe.
+ public abstract void onCreateTranslationSession(@NonNull TranslationSpec sourceSpec,
+ @NonNull TranslationSpec destSpec, int sessionId);
+
+ /**
+ * TODO: fill in javadoc.
+ *
+ * @param sessionId
+ */
+ public abstract void onFinishTranslationSession(int sessionId);
+
+ /**
+ * TODO: fill in javadoc.
+ *
+ * @param request
+ * @param sessionId
+ * @param callback
+ * @param cancellationSignal
+ */
+ public abstract void onTranslationRequest(@NonNull TranslationRequest request, int sessionId,
+ @NonNull CancellationSignal cancellationSignal,
+ @NonNull OnTranslationResultCallback callback);
+
+ // TODO(b/176464808): Need to handle client dying case
+
+ // TODO(b/176464808): Need to handle the failure case. e.g. if the specs does not support
+
+ private void handleOnCreateTranslationSession(@NonNull TranslationSpec sourceSpec,
+ @NonNull TranslationSpec destSpec, int sessionId, IResultReceiver resultReceiver) {
+ try {
+ final Bundle extras = new Bundle();
+ extras.putBinder(EXTRA_SERVICE_BINDER, mClientInterface.asBinder());
+ extras.putInt(EXTRA_SESSION_ID, sessionId);
+ resultReceiver.send(0, extras);
+ } catch (RemoteException e) {
+ Log.w(TAG, "RemoteException sending client interface: " + e);
+ }
+ onCreateTranslationSession(sourceSpec, destSpec, sessionId);
+ }
+}
diff --git a/core/java/android/service/translation/TranslationServiceInfo.java b/core/java/android/service/translation/TranslationServiceInfo.java
new file mode 100644
index 0000000..18cc29d
--- /dev/null
+++ b/core/java/android/service/translation/TranslationServiceInfo.java
@@ -0,0 +1,173 @@
+/*
+ * 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.service.translation;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.os.RemoteException;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * {@link ServiceInfo} and meta-data about an {@link TranslationService}.
+ *
+ * @hide
+ */
+public final class TranslationServiceInfo {
+
+ private static final String TAG = "TranslationServiceInfo";
+ private static final String XML_TAG_SERVICE = "translation-service";
+
+ @NonNull
+ private final ServiceInfo mServiceInfo;
+
+ @Nullable
+ private final String mSettingsActivity;
+
+ private static ServiceInfo getServiceInfoOrThrow(ComponentName comp, boolean isTemp,
+ @UserIdInt int userId) throws PackageManager.NameNotFoundException {
+ int flags = PackageManager.GET_META_DATA;
+ if (!isTemp) {
+ flags |= PackageManager.MATCH_SYSTEM_ONLY;
+ }
+
+ ServiceInfo si = null;
+ try {
+ si = AppGlobals.getPackageManager().getServiceInfo(comp, flags, userId);
+ } catch (RemoteException e) {
+ }
+ if (si == null) {
+ throw new NameNotFoundException("Could not get serviceInfo for "
+ + (isTemp ? " (temp)" : "(default system)")
+ + " " + comp.flattenToShortString());
+ }
+ return si;
+ }
+
+ @NonNull
+ public ServiceInfo getServiceInfo() {
+ return mServiceInfo;
+ }
+
+ @Nullable
+ public String getSettingsActivity() {
+ return mSettingsActivity;
+ }
+
+ public TranslationServiceInfo(@NonNull Context context, @NonNull ComponentName comp,
+ boolean isTemporaryService, @UserIdInt int userId)
+ throws PackageManager.NameNotFoundException {
+ this(context, getServiceInfoOrThrow(comp, isTemporaryService, userId));
+ }
+
+ private TranslationServiceInfo(@NonNull Context context, @NonNull ServiceInfo si) {
+ // Check for permission.
+ if (!Manifest.permission.BIND_TRANSLATION_SERVICE.equals(si.permission)) {
+ Slog.w(TAG, "TranslationServiceInfo from '" + si.packageName
+ + "' does not require permission "
+ + Manifest.permission.BIND_CONTENT_CAPTURE_SERVICE);
+ throw new SecurityException("Service does not require permission "
+ + Manifest.permission.BIND_CONTENT_CAPTURE_SERVICE);
+ }
+
+ mServiceInfo = si;
+
+ // Get the metadata, if declared.
+ // TODO: Try to find more easier way to do this.
+ final XmlResourceParser parser = si.loadXmlMetaData(context.getPackageManager(),
+ TranslationService.SERVICE_META_DATA);
+ if (parser == null) {
+ mSettingsActivity = null;
+ return;
+ }
+
+ String settingsActivity = null;
+
+ try {
+ final Resources resources = context.getPackageManager().getResourcesForApplication(
+ si.applicationInfo);
+
+ int type = 0;
+ while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) {
+ type = parser.next();
+ }
+
+ if (XML_TAG_SERVICE.equals(parser.getName())) {
+ final AttributeSet allAttributes = Xml.asAttributeSet(parser);
+ TypedArray afsAttributes = null;
+ try {
+ afsAttributes = resources.obtainAttributes(allAttributes,
+ com.android.internal.R.styleable.TranslationService);
+ settingsActivity = afsAttributes.getString(
+ R.styleable.ContentCaptureService_settingsActivity);
+ } finally {
+ if (afsAttributes != null) {
+ afsAttributes.recycle();
+ }
+ }
+ } else {
+ Log.e(TAG, "Meta-data does not start with translation-service tag");
+ }
+ } catch (PackageManager.NameNotFoundException | IOException | XmlPullParserException e) {
+ Log.e(TAG, "Error parsing auto fill service meta-data", e);
+ }
+
+ mSettingsActivity = settingsActivity;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append(getClass().getSimpleName());
+ builder.append("[").append(mServiceInfo);
+ builder.append(", settings:").append(mSettingsActivity);
+ return builder.toString();
+ }
+
+ /**
+ * Dumps the service information.
+ */
+ public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
+ pw.print(prefix);
+ pw.print("Component: ");
+ pw.println(getServiceInfo().getComponentName());
+ pw.print(prefix);
+ pw.print("Settings: ");
+ pw.println(mSettingsActivity);
+ }
+}
diff --git a/core/java/android/util/SparseArray.java b/core/java/android/util/SparseArray.java
index dae760f..86120d1 100644
--- a/core/java/android/util/SparseArray.java
+++ b/core/java/android/util/SparseArray.java
@@ -241,6 +241,14 @@
}
/**
+ * Alias for {@link #put(int, Object)} to support Kotlin [index]= operator.
+ * @see #put(int, Object)
+ */
+ public void set(int key, E value) {
+ put(key, value);
+ }
+
+ /**
* Adds a mapping from the specified key to the specified value,
* replacing the previous mapping from the specified key if there
* was one.
diff --git a/core/java/android/view/translation/ITranslationDirectManager.aidl b/core/java/android/view/translation/ITranslationDirectManager.aidl
new file mode 100644
index 0000000..358f99a
--- /dev/null
+++ b/core/java/android/view/translation/ITranslationDirectManager.aidl
@@ -0,0 +1,33 @@
+/*
+ * 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.view.translation;
+
+import android.service.translation.TranslationRequest;
+import android.service.translation.ITranslationCallback;
+import com.android.internal.os.IResultReceiver;
+
+/**
+ * Interface between an app (TranslationManager / Translator) and the remote TranslationService
+ * providing the TranslationService implementation.
+ *
+ * @hide
+ */
+oneway interface ITranslationDirectManager {
+ void onTranslationRequest(in TranslationRequest request, int sessionId,
+ in ITranslationCallback callback, in IResultReceiver receiver);
+ void onFinishTranslationSession(int sessionId);
+}
diff --git a/core/java/android/view/translation/ITranslationManager.aidl b/core/java/android/view/translation/ITranslationManager.aidl
new file mode 100644
index 0000000..73addf4
--- /dev/null
+++ b/core/java/android/view/translation/ITranslationManager.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.view.translation;
+
+import android.service.translation.TranslationRequest;
+import android.view.translation.TranslationSpec;
+import com.android.internal.os.IResultReceiver;
+
+/**
+ * Mediator between apps being translated and translation service implementation.
+ *
+ * {@hide}
+ */
+oneway interface ITranslationManager {
+ void getSupportedLocales(in IResultReceiver receiver, int userId);
+ void onSessionCreated(in TranslationSpec sourceSpec, in TranslationSpec destSpec,
+ int sessionId, in IResultReceiver receiver, int userId);
+}
diff --git a/core/java/android/view/translation/OWNERS b/core/java/android/view/translation/OWNERS
new file mode 100644
index 0000000..a1e663a
--- /dev/null
+++ b/core/java/android/view/translation/OWNERS
@@ -0,0 +1,8 @@
+# Bug component: 994311
+
+adamhe@google.com
+augale@google.com
+joannechung@google.com
+lpeter@google.com
+svetoslavganov@google.com
+tymtsai@google.com
diff --git a/core/java/android/view/translation/TranslationData.aidl b/core/java/android/view/translation/TranslationData.aidl
new file mode 100644
index 0000000..40f21a6
--- /dev/null
+++ b/core/java/android/view/translation/TranslationData.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.view.translation;
+
+parcelable TranslationData;
diff --git a/core/java/android/view/translation/TranslationManager.java b/core/java/android/view/translation/TranslationManager.java
new file mode 100644
index 0000000..6554e1a
--- /dev/null
+++ b/core/java/android/view/translation/TranslationManager.java
@@ -0,0 +1,201 @@
+/*
+ * 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.view.translation;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresFeature;
+import android.annotation.SystemService;
+import android.annotation.WorkerThread;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.service.translation.TranslationService;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Pair;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.SyncResultReceiver;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * The {@link TranslationManager} class provides ways for apps to integrate and use the
+ * translation framework.
+ *
+ * <p>The TranslationManager manages {@link Translator}s and help bridge client calls to
+ * the server {@link android.service.translation.TranslationService} </p>
+ */
+@SystemService(Context.TRANSLATION_MANAGER_SERVICE)
+@RequiresFeature(PackageManager.FEATURE_TRANSLATION)
+public final class TranslationManager {
+
+ private static final String TAG = "TranslationManager";
+
+ /**
+ * Timeout for calls to system_server.
+ */
+ static final int SYNC_CALLS_TIMEOUT_MS = 5000;
+ /**
+ * The result code from result receiver success.
+ * @hide
+ */
+ public static final int STATUS_SYNC_CALL_SUCCESS = 1;
+ /**
+ * The result code from result receiver fail.
+ * @hide
+ */
+ public static final int STATUS_SYNC_CALL_FAIL = 2;
+
+ private static final Random ID_GENERATOR = new Random();
+ private final Object mLock = new Object();
+
+ @NonNull
+ private final Context mContext;
+
+ private final ITranslationManager mService;
+
+ @Nullable
+ @GuardedBy("mLock")
+ private ITranslationDirectManager mDirectServiceBinder;
+
+ @NonNull
+ @GuardedBy("mLock")
+ private final SparseArray<Translator> mTranslators = new SparseArray<>();
+
+ @NonNull
+ @GuardedBy("mLock")
+ private final ArrayMap<Pair<TranslationSpec, TranslationSpec>, Integer> mTranslatorIds =
+ new ArrayMap<>();
+
+ @NonNull
+ private final Handler mHandler;
+
+ private static final AtomicInteger sAvailableRequestId = new AtomicInteger(1);
+
+ /**
+ * @hide
+ */
+ public TranslationManager(@NonNull Context context, ITranslationManager service) {
+ mContext = Objects.requireNonNull(context, "context cannot be null");
+ mService = service;
+
+ mHandler = Handler.createAsync(Looper.getMainLooper());
+ }
+
+ /**
+ * Create a Translator for translation.
+ *
+ * <p><strong>NOTE: </strong>Call on a worker thread.
+ *
+ * @param sourceSpec {@link TranslationSpec} for the data to be translated.
+ * @param destSpec {@link TranslationSpec} for the translated data.
+ * @return a {@link Translator} to be used for calling translation APIs.
+ */
+ @Nullable
+ @WorkerThread
+ public Translator createTranslator(@NonNull TranslationSpec sourceSpec,
+ @NonNull TranslationSpec destSpec) {
+ Objects.requireNonNull(sourceSpec, "sourceSpec cannot be null");
+ Objects.requireNonNull(sourceSpec, "destSpec cannot be null");
+
+ synchronized (mLock) {
+ // TODO(b/176464808): Disallow multiple Translator now, it will throw
+ // IllegalStateException. Need to discuss if we can allow multiple Translators.
+ final Pair<TranslationSpec, TranslationSpec> specs = new Pair<>(sourceSpec, destSpec);
+ if (mTranslatorIds.containsKey(specs)) {
+ return mTranslators.get(mTranslatorIds.get(specs));
+ }
+
+ int translatorId;
+ do {
+ translatorId = Math.abs(ID_GENERATOR.nextInt());
+ } while (translatorId == 0 || mTranslators.indexOfKey(translatorId) >= 0);
+
+ final Translator newTranslator = new Translator(mContext, sourceSpec, destSpec,
+ translatorId, this, mHandler, mService);
+ // Start the Translator session and wait for the result
+ newTranslator.start();
+ try {
+ if (!newTranslator.isSessionCreated()) {
+ return null;
+ }
+ mTranslators.put(translatorId, newTranslator);
+ mTranslatorIds.put(specs, translatorId);
+ return newTranslator;
+ } catch (Translator.ServiceBinderReceiver.TimeoutException e) {
+ // TODO(b/176464808): maybe make SyncResultReceiver.TimeoutException constructor
+ // public and use it.
+ Log.e(TAG, "Timed out getting create session: " + e);
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Returns a list of locales supported by the {@link TranslationService}.
+ *
+ * <p><strong>NOTE: </strong>Call on a worker thread.
+ *
+ * TODO: Change to correct language/locale format
+ */
+ @NonNull
+ @WorkerThread
+ public List<String> getSupportedLocales() {
+ try {
+ // TODO: implement it
+ final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
+ mService.getSupportedLocales(receiver, mContext.getUserId());
+ int resutCode = receiver.getIntResult();
+ if (resutCode != STATUS_SYNC_CALL_SUCCESS) {
+ return Collections.emptyList();
+ }
+ return receiver.getParcelableResult();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (SyncResultReceiver.TimeoutException e) {
+ Log.e(TAG, "Timed out getting supported locales: " + e);
+ return Collections.emptyList();
+ }
+ }
+
+ void removeTranslator(int id) {
+ synchronized (mLock) {
+ mTranslators.remove(id);
+ for (int i = 0; i < mTranslatorIds.size(); i++) {
+ if (mTranslatorIds.valueAt(i) == id) {
+ mTranslatorIds.removeAt(i);
+ break;
+ }
+ }
+ }
+ }
+
+ AtomicInteger getAvailableRequestId() {
+ synchronized (mLock) {
+ return sAvailableRequestId;
+ }
+ }
+}
diff --git a/core/java/android/view/translation/TranslationRequest.aidl b/core/java/android/view/translation/TranslationRequest.aidl
new file mode 100644
index 0000000..c34bf30
--- /dev/null
+++ b/core/java/android/view/translation/TranslationRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.view.translation;
+
+parcelable TranslationRequest;
diff --git a/core/java/android/view/translation/TranslationRequest.java b/core/java/android/view/translation/TranslationRequest.java
new file mode 100644
index 0000000..a5e3f75
--- /dev/null
+++ b/core/java/android/view/translation/TranslationRequest.java
@@ -0,0 +1,215 @@
+/*
+ * 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.view.translation;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcelable;
+import android.view.autofill.AutofillId;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Wrapper class for data to be translated by {@link android.service.translation.TranslationService}
+ */
+@DataClass(genToString = true, genBuilder = true)
+public final class TranslationRequest implements Parcelable {
+
+ @Nullable
+ private final AutofillId mAutofillId;
+
+ @Nullable
+ private final CharSequence mTranslationText;
+
+ public TranslationRequest(@Nullable CharSequence text) {
+ mAutofillId = null;
+ mTranslationText = text;
+ }
+
+ private static CharSequence defaultTranslationText() {
+ return null;
+ }
+
+ private static AutofillId defaultAutofillId() {
+ return null;
+ }
+
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/translation/TranslationRequest.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @DataClass.Generated.Member
+ /* package-private */ TranslationRequest(
+ @Nullable AutofillId autofillId,
+ @Nullable CharSequence translationText) {
+ this.mAutofillId = autofillId;
+ this.mTranslationText = translationText;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable AutofillId getAutofillId() {
+ return mAutofillId;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable CharSequence getTranslationText() {
+ return mTranslationText;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "TranslationRequest { " +
+ "autofillId = " + mAutofillId + ", " +
+ "translationText = " + mTranslationText +
+ " }";
+ }
+
+ @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) { ... }
+
+ byte flg = 0;
+ if (mAutofillId != null) flg |= 0x1;
+ if (mTranslationText != null) flg |= 0x2;
+ dest.writeByte(flg);
+ if (mAutofillId != null) dest.writeTypedObject(mAutofillId, flags);
+ if (mTranslationText != null) dest.writeCharSequence(mTranslationText);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ TranslationRequest(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ AutofillId autofillId = (flg & 0x1) == 0 ? null : (AutofillId) in.readTypedObject(AutofillId.CREATOR);
+ CharSequence translationText = (flg & 0x2) == 0 ? null : (CharSequence) in.readCharSequence();
+
+ this.mAutofillId = autofillId;
+ this.mTranslationText = translationText;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<TranslationRequest> CREATOR
+ = new Parcelable.Creator<TranslationRequest>() {
+ @Override
+ public TranslationRequest[] newArray(int size) {
+ return new TranslationRequest[size];
+ }
+
+ @Override
+ public TranslationRequest createFromParcel(@NonNull android.os.Parcel in) {
+ return new TranslationRequest(in);
+ }
+ };
+
+ /**
+ * A builder for {@link TranslationRequest}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private @Nullable AutofillId mAutofillId;
+ private @Nullable CharSequence mTranslationText;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull Builder setAutofillId(@NonNull AutofillId value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mAutofillId = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull Builder setTranslationText(@NonNull CharSequence value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mTranslationText = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull TranslationRequest build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x1) == 0) {
+ mAutofillId = defaultAutofillId();
+ }
+ if ((mBuilderFieldsSet & 0x2) == 0) {
+ mTranslationText = defaultTranslationText();
+ }
+ TranslationRequest o = new TranslationRequest(
+ mAutofillId,
+ mTranslationText);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x4) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1610060189421L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/view/translation/TranslationRequest.java",
+ inputSignatures = "private final @android.annotation.Nullable android.view.autofill.AutofillId mAutofillId\nprivate final @android.annotation.Nullable java.lang.CharSequence mTranslationText\nprivate static java.lang.CharSequence defaultTranslationText()\nprivate static android.view.autofill.AutofillId defaultAutofillId()\nclass TranslationRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genBuilder=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/view/translation/TranslationResponse.aidl b/core/java/android/view/translation/TranslationResponse.aidl
new file mode 100644
index 0000000..e5350bb
--- /dev/null
+++ b/core/java/android/view/translation/TranslationResponse.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.view.translation;
+
+parcelable TranslationResponse;
diff --git a/core/java/android/view/translation/TranslationResponse.java b/core/java/android/view/translation/TranslationResponse.java
new file mode 100644
index 0000000..d29063f
--- /dev/null
+++ b/core/java/android/view/translation/TranslationResponse.java
@@ -0,0 +1,311 @@
+/*
+ * 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.view.translation;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.service.translation.TranslationService;
+
+import com.android.internal.util.DataClass;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Response from the {@link TranslationService}, which contains the translated result.
+ */
+@DataClass(genBuilder = true, genToString = true, genHiddenConstDefs = true)
+public final class TranslationResponse implements Parcelable {
+
+ /**
+ * The {@link TranslationService} was successful in translating.
+ */
+ public static final int TRANSLATION_STATUS_SUCCESS = 0;
+ /**
+ * The {@link TranslationService} returned unknown translation result.
+ */
+ public static final int TRANSLATION_STATUS_UNKNOWN_ERROR = 1;
+ /**
+ * The language of the request is not available to be translated.
+ */
+ public static final int TRANSLATION_STATUS_LANGUAGE_UNAVAILABLE = 2;
+
+ /**
+ * The translation result status code.
+ */
+ private final @TranslationStatus int mTranslationStatus;
+ /**
+ * The translation results. If there is no translation result, set it with an empty list.
+ */
+ @NonNull
+ private List<TranslationRequest> mTranslations = new ArrayList();
+
+
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/translation/TranslationResponse.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /** @hide */
+ @IntDef(prefix = "TRANSLATION_STATUS_", value = {
+ TRANSLATION_STATUS_SUCCESS,
+ TRANSLATION_STATUS_UNKNOWN_ERROR,
+ TRANSLATION_STATUS_LANGUAGE_UNAVAILABLE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface TranslationStatus {}
+
+ /** @hide */
+ @DataClass.Generated.Member
+ public static String translationStatusToString(@TranslationStatus int value) {
+ switch (value) {
+ case TRANSLATION_STATUS_SUCCESS:
+ return "TRANSLATION_STATUS_SUCCESS";
+ case TRANSLATION_STATUS_UNKNOWN_ERROR:
+ return "TRANSLATION_STATUS_UNKNOWN_ERROR";
+ case TRANSLATION_STATUS_LANGUAGE_UNAVAILABLE:
+ return "TRANSLATION_STATUS_LANGUAGE_UNAVAILABLE";
+ default: return Integer.toHexString(value);
+ }
+ }
+
+ @DataClass.Generated.Member
+ /* package-private */ TranslationResponse(
+ @TranslationStatus int translationStatus,
+ @NonNull List<TranslationRequest> translations) {
+ this.mTranslationStatus = translationStatus;
+
+ if (!(mTranslationStatus == TRANSLATION_STATUS_SUCCESS)
+ && !(mTranslationStatus == TRANSLATION_STATUS_UNKNOWN_ERROR)
+ && !(mTranslationStatus == TRANSLATION_STATUS_LANGUAGE_UNAVAILABLE)) {
+ throw new java.lang.IllegalArgumentException(
+ "translationStatus was " + mTranslationStatus + " but must be one of: "
+ + "TRANSLATION_STATUS_SUCCESS(" + TRANSLATION_STATUS_SUCCESS + "), "
+ + "TRANSLATION_STATUS_UNKNOWN_ERROR(" + TRANSLATION_STATUS_UNKNOWN_ERROR + "), "
+ + "TRANSLATION_STATUS_LANGUAGE_UNAVAILABLE(" + TRANSLATION_STATUS_LANGUAGE_UNAVAILABLE + ")");
+ }
+
+ this.mTranslations = translations;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mTranslations);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The translation result status code.
+ */
+ @DataClass.Generated.Member
+ public @TranslationStatus int getTranslationStatus() {
+ return mTranslationStatus;
+ }
+
+ /**
+ * The translation results. If there is no translation result, set it with an empty list.
+ */
+ @DataClass.Generated.Member
+ public @NonNull List<TranslationRequest> getTranslations() {
+ return mTranslations;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "TranslationResponse { " +
+ "translationStatus = " + translationStatusToString(mTranslationStatus) + ", " +
+ "translations = " + mTranslations +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeInt(mTranslationStatus);
+ dest.writeParcelableList(mTranslations, flags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ TranslationResponse(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ int translationStatus = in.readInt();
+ List<TranslationRequest> translations = new ArrayList<>();
+ in.readParcelableList(translations, TranslationRequest.class.getClassLoader());
+
+ this.mTranslationStatus = translationStatus;
+
+ if (!(mTranslationStatus == TRANSLATION_STATUS_SUCCESS)
+ && !(mTranslationStatus == TRANSLATION_STATUS_UNKNOWN_ERROR)
+ && !(mTranslationStatus == TRANSLATION_STATUS_LANGUAGE_UNAVAILABLE)) {
+ throw new java.lang.IllegalArgumentException(
+ "translationStatus was " + mTranslationStatus + " but must be one of: "
+ + "TRANSLATION_STATUS_SUCCESS(" + TRANSLATION_STATUS_SUCCESS + "), "
+ + "TRANSLATION_STATUS_UNKNOWN_ERROR(" + TRANSLATION_STATUS_UNKNOWN_ERROR + "), "
+ + "TRANSLATION_STATUS_LANGUAGE_UNAVAILABLE(" + TRANSLATION_STATUS_LANGUAGE_UNAVAILABLE + ")");
+ }
+
+ this.mTranslations = translations;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mTranslations);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<TranslationResponse> CREATOR
+ = new Parcelable.Creator<TranslationResponse>() {
+ @Override
+ public TranslationResponse[] newArray(int size) {
+ return new TranslationResponse[size];
+ }
+
+ @Override
+ public TranslationResponse createFromParcel(@NonNull Parcel in) {
+ return new TranslationResponse(in);
+ }
+ };
+
+ /**
+ * A builder for {@link TranslationResponse}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private @TranslationStatus int mTranslationStatus;
+ private @NonNull List<TranslationRequest> mTranslations;
+
+ private long mBuilderFieldsSet = 0L;
+
+ /**
+ * Creates a new Builder.
+ *
+ * @param translationStatus
+ * The translation result status code.
+ */
+ public Builder(
+ @TranslationStatus int translationStatus) {
+ mTranslationStatus = translationStatus;
+
+ if (!(mTranslationStatus == TRANSLATION_STATUS_SUCCESS)
+ && !(mTranslationStatus == TRANSLATION_STATUS_UNKNOWN_ERROR)
+ && !(mTranslationStatus == TRANSLATION_STATUS_LANGUAGE_UNAVAILABLE)) {
+ throw new java.lang.IllegalArgumentException(
+ "translationStatus was " + mTranslationStatus + " but must be one of: "
+ + "TRANSLATION_STATUS_SUCCESS(" + TRANSLATION_STATUS_SUCCESS + "), "
+ + "TRANSLATION_STATUS_UNKNOWN_ERROR(" + TRANSLATION_STATUS_UNKNOWN_ERROR + "), "
+ + "TRANSLATION_STATUS_LANGUAGE_UNAVAILABLE(" + TRANSLATION_STATUS_LANGUAGE_UNAVAILABLE + ")");
+ }
+
+ }
+
+ /**
+ * The translation result status code.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setTranslationStatus(@TranslationStatus int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mTranslationStatus = value;
+ return this;
+ }
+
+ /**
+ * The translation results. If there is no translation result, set it with an empty list.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setTranslations(@NonNull List<TranslationRequest> value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mTranslations = value;
+ return this;
+ }
+
+ /** @see #setTranslations */
+ @DataClass.Generated.Member
+ public @NonNull Builder addTranslations(@NonNull TranslationRequest value) {
+ // You can refine this method's name by providing item's singular name, e.g.:
+ // @DataClass.PluralOf("item")) mItems = ...
+
+ if (mTranslations == null) setTranslations(new ArrayList<>());
+ mTranslations.add(value);
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull TranslationResponse build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x2) == 0) {
+ mTranslations = new ArrayList();
+ }
+ TranslationResponse o = new TranslationResponse(
+ mTranslationStatus,
+ mTranslations);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x4) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1609973911361L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/view/translation/TranslationResponse.java",
+ inputSignatures = "public static final int TRANSLATION_STATUS_SUCCESS\npublic static final int TRANSLATION_STATUS_UNKNOWN_ERROR\npublic static final int TRANSLATION_STATUS_LANGUAGE_UNAVAILABLE\nprivate final @android.view.translation.TranslationResponse.TranslationStatus int mTranslationStatus\nprivate @android.annotation.NonNull java.util.List<android.view.translation.TranslationRequest> mTranslations\nclass TranslationResponse extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genToString=true, genHiddenConstDefs=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/view/translation/TranslationSpec.aidl b/core/java/android/view/translation/TranslationSpec.aidl
new file mode 100644
index 0000000..875d798
--- /dev/null
+++ b/core/java/android/view/translation/TranslationSpec.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.view.translation;
+
+parcelable TranslationSpec;
diff --git a/core/java/android/view/translation/TranslationSpec.java b/core/java/android/view/translation/TranslationSpec.java
new file mode 100644
index 0000000..ab1bc47
--- /dev/null
+++ b/core/java/android/view/translation/TranslationSpec.java
@@ -0,0 +1,189 @@
+/*
+ * 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.view.translation;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Specs and additional info for the translation data.
+ *
+ * <p>This spec help specify information such as the language/locale for the translation, as well
+ * as the data format for the translation (text, audio, etc.)</p>
+ */
+@DataClass(genEqualsHashCode = true, genHiddenConstDefs = true)
+public final class TranslationSpec implements Parcelable {
+
+ /** Data format for translation is text. */
+ public static final int DATA_FORMAT_TEXT = 1;
+
+ /** @hide */
+ @android.annotation.IntDef(prefix = "DATA_FORMAT_", value = {
+ DATA_FORMAT_TEXT
+ })
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface DataFormat {}
+
+ /**
+ * String representation of language codes e.g. "en", "es", etc.
+ */
+ private final @NonNull String mLanguage;
+
+ private final @DataFormat int mDataFormat;
+
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/translation/TranslationSpec.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new TranslationSpec.
+ *
+ * @param language
+ * String representation of language codes e.g. "en", "es", etc.
+ */
+ @DataClass.Generated.Member
+ public TranslationSpec(
+ @NonNull String language,
+ @DataFormat int dataFormat) {
+ this.mLanguage = language;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mLanguage);
+ this.mDataFormat = dataFormat;
+ com.android.internal.util.AnnotationValidations.validate(
+ DataFormat.class, null, mDataFormat);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * String representation of language codes e.g. "en", "es", etc.
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getLanguage() {
+ return mLanguage;
+ }
+
+ @DataClass.Generated.Member
+ public @DataFormat int getDataFormat() {
+ return mDataFormat;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@android.annotation.Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(TranslationSpec other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ TranslationSpec that = (TranslationSpec) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mLanguage, that.mLanguage)
+ && mDataFormat == that.mDataFormat;
+ }
+
+ @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 + java.util.Objects.hashCode(mLanguage);
+ _hash = 31 * _hash + mDataFormat;
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeString(mLanguage);
+ dest.writeInt(mDataFormat);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ TranslationSpec(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ String language = in.readString();
+ int dataFormat = in.readInt();
+
+ this.mLanguage = language;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mLanguage);
+ this.mDataFormat = dataFormat;
+ com.android.internal.util.AnnotationValidations.validate(
+ DataFormat.class, null, mDataFormat);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<TranslationSpec> CREATOR
+ = new Parcelable.Creator<TranslationSpec>() {
+ @Override
+ public TranslationSpec[] newArray(int size) {
+ return new TranslationSpec[size];
+ }
+
+ @Override
+ public TranslationSpec createFromParcel(@NonNull Parcel in) {
+ return new TranslationSpec(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1609964630624L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/view/translation/TranslationSpec.java",
+ inputSignatures = "public static final int DATA_FORMAT_TEXT\nprivate final @android.annotation.NonNull java.lang.String mLanguage\nprivate final @android.view.translation.TranslationSpec.DataFormat int mDataFormat\nclass TranslationSpec extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genHiddenConstDefs=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/view/translation/Translator.java b/core/java/android/view/translation/Translator.java
new file mode 100644
index 0000000..675f32b
--- /dev/null
+++ b/core/java/android/view/translation/Translator.java
@@ -0,0 +1,298 @@
+/*
+ * 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.view.translation;
+
+import static android.view.translation.TranslationManager.STATUS_SYNC_CALL_FAIL;
+import static android.view.translation.TranslationManager.SYNC_CALLS_TIMEOUT_MS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.WorkerThread;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.IResultReceiver;
+import com.android.internal.util.SyncResultReceiver;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * The {@link Translator} for translation, defined by a source and a dest {@link TranslationSpec}.
+ */
+@SuppressLint("NotCloseable")
+public class Translator {
+
+ private static final String TAG = "Translator";
+
+ // TODO: make this configurable and cross the Translation component
+ private static boolean sDEBUG = false;
+
+ private final Object mLock = new Object();
+
+ private int mId;
+
+ @NonNull
+ private final Context mContext;
+
+ @NonNull
+ private final TranslationSpec mSourceSpec;
+
+ @NonNull
+ private final TranslationSpec mDestSpec;
+
+ @NonNull
+ private final TranslationManager mManager;
+
+ @NonNull
+ private final Handler mHandler;
+
+ /**
+ * Interface to the system_server binder object.
+ */
+ private ITranslationManager mSystemServerBinder;
+
+ /**
+ * Direct interface to the TranslationService binder object.
+ */
+ @Nullable
+ private ITranslationDirectManager mDirectServiceBinder;
+
+ @NonNull
+ private final ServiceBinderReceiver mServiceBinderReceiver;
+
+ @GuardedBy("mLock")
+ private boolean mDestroyed;
+
+ /**
+ * Name of the {@link IResultReceiver} extra used to pass the binder interface to Translator.
+ * @hide
+ */
+ public static final String EXTRA_SERVICE_BINDER = "binder";
+ /**
+ * Name of the extra used to pass the session id to Translator.
+ * @hide
+ */
+ public static final String EXTRA_SESSION_ID = "sessionId";
+
+ static class ServiceBinderReceiver extends IResultReceiver.Stub {
+ private final WeakReference<Translator> mTranslator;
+ private final CountDownLatch mLatch = new CountDownLatch(1);
+ private int mSessionId;
+
+ ServiceBinderReceiver(Translator translator) {
+ mTranslator = new WeakReference<>(translator);
+ }
+
+ int getSessionStateResult() throws TimeoutException {
+ try {
+ if (!mLatch.await(SYNC_CALLS_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ throw new TimeoutException(
+ "Session not created in " + SYNC_CALLS_TIMEOUT_MS + "ms");
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new TimeoutException("Session not created because interrupted");
+ }
+ return mSessionId;
+ }
+
+ @Override
+ public void send(int resultCode, Bundle resultData) {
+ if (resultCode == STATUS_SYNC_CALL_FAIL) {
+ mLatch.countDown();
+ return;
+ }
+ mSessionId = resultData.getInt(EXTRA_SESSION_ID);
+ final Translator translator = mTranslator.get();
+ if (translator == null) {
+ Log.w(TAG, "received result after session is finished");
+ return;
+ }
+ final IBinder binder;
+ if (resultData != null) {
+ binder = resultData.getBinder(EXTRA_SERVICE_BINDER);
+ if (binder == null) {
+ Log.wtf(TAG, "No " + EXTRA_SERVICE_BINDER + " extra result");
+ return;
+ }
+ } else {
+ binder = null;
+ }
+ translator.setServiceBinder(binder);
+ mLatch.countDown();
+ }
+
+ // TODO(b/176464808): maybe make SyncResultReceiver.TimeoutException constructor public
+ // and use it.
+ static final class TimeoutException extends Exception {
+ private TimeoutException(String msg) {
+ super(msg);
+ }
+ }
+ }
+
+ /**
+ * Create the Translator.
+ *
+ * @hide
+ */
+ public Translator(@NonNull Context context,
+ @NonNull TranslationSpec sourceSpec,
+ @NonNull TranslationSpec destSpec, int sessionId,
+ @NonNull TranslationManager translationManager, @NonNull Handler handler,
+ @Nullable ITranslationManager systemServerBinder) {
+ mContext = context;
+ mSourceSpec = sourceSpec;
+ mDestSpec = destSpec;
+ mId = sessionId;
+ mManager = translationManager;
+ mHandler = handler;
+ mSystemServerBinder = systemServerBinder;
+ mServiceBinderReceiver = new ServiceBinderReceiver(this);
+ }
+
+ /**
+ * Starts this Translator session.
+ */
+ void start() {
+ try {
+ mSystemServerBinder.onSessionCreated(mSourceSpec, mDestSpec, mId,
+ mServiceBinderReceiver, mContext.getUserId());
+ } catch (RemoteException e) {
+ Log.w(TAG, "RemoteException calling startSession(): " + e);
+ }
+ }
+
+ /**
+ * Wait this Translator session created.
+ *
+ * @return {@code true} if the session is created successfully.
+ */
+ boolean isSessionCreated() throws ServiceBinderReceiver.TimeoutException {
+ int receivedId = mServiceBinderReceiver.getSessionStateResult();
+ return receivedId > 0;
+ }
+
+ private int getNextRequestId() {
+ // Get from manager to keep the request id unique to different Translators
+ return mManager.getAvailableRequestId().getAndIncrement();
+ }
+
+ private void setServiceBinder(@Nullable IBinder binder) {
+ synchronized (mLock) {
+ if (mDirectServiceBinder != null) {
+ return;
+ }
+ if (binder != null) {
+ mDirectServiceBinder = ITranslationDirectManager.Stub.asInterface(binder);
+ }
+ }
+ }
+
+ /** @hide */
+ public int getTranslatorId() {
+ return mId;
+ }
+
+ /**
+ * Requests a translation for the provided {@link TranslationRequest} using the Translator's
+ * source spec and destination spec.
+ *
+ * <p><strong>NOTE: </strong>Call on a worker thread.
+ *
+ * @param request {@link TranslationRequest} request to be translated.
+ *
+ * @return {@link TranslationRequest} containing translated request,
+ * or null if translation could not be done.
+ * @throws IllegalStateException if this TextClassification session was destroyed when calls
+ */
+ @Nullable
+ @WorkerThread
+ public TranslationResponse translate(@NonNull TranslationRequest request) {
+ Objects.requireNonNull(request, "Translation request cannot be null");
+ if (isDestroyed()) {
+ // TODO(b/176464808): Disallow multiple Translator now, it will throw
+ // IllegalStateException. Need to discuss if we can allow multiple Translators.
+ throw new IllegalStateException(
+ "This translator has been destroyed");
+ }
+ final ArrayList<TranslationRequest> requests = new ArrayList<>();
+ requests.add(request);
+ final android.service.translation.TranslationRequest internalRequest =
+ new android.service.translation.TranslationRequest
+ .Builder(getNextRequestId(), mSourceSpec, mDestSpec, requests)
+ .build();
+
+ TranslationResponse response = null;
+ try {
+ final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
+ mDirectServiceBinder.onTranslationRequest(internalRequest, mId, null, receiver);
+
+ response = receiver.getParcelableResult();
+ } catch (RemoteException e) {
+ Log.w(TAG, "RemoteException calling requestTranslate(): " + e);
+ } catch (SyncResultReceiver.TimeoutException e) {
+ Log.e(TAG, "Timed out calling requestTranslate: " + e);
+ }
+ if (sDEBUG) {
+ Log.v(TAG, "Receive translation response: " + response);
+ }
+ return response;
+ }
+
+ /**
+ * Destroy this Translator.
+ */
+ public void destroy() {
+ synchronized (mLock) {
+ if (mDestroyed) {
+ return;
+ }
+ mDestroyed = true;
+ try {
+ mDirectServiceBinder.onFinishTranslationSession(mId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "RemoteException calling onSessionFinished");
+ }
+ mDirectServiceBinder = null;
+ mManager.removeTranslator(mId);
+ }
+ }
+
+ /**
+ * Returns whether or not this Translator has been destroyed.
+ *
+ * @see #destroy()
+ */
+ public boolean isDestroyed() {
+ synchronized (mLock) {
+ return mDestroyed;
+ }
+ }
+
+ // TODO: add methods for UI-toolkit case.
+}
diff --git a/core/java/android/window/ITaskOrganizerController.aidl b/core/java/android/window/ITaskOrganizerController.aidl
index 4a43a43..2d0211e 100644
--- a/core/java/android/window/ITaskOrganizerController.aidl
+++ b/core/java/android/window/ITaskOrganizerController.aidl
@@ -56,12 +56,6 @@
WindowContainerToken getImeTarget(int display);
/**
- * Set's the root task to launch new tasks into on a display. {@code null} means no launch root
- * and thus new tasks just end up directly on the display.
- */
- void setLaunchRoot(int displayId, in WindowContainerToken root);
-
- /**
* Requests that the given task organizer is notified when back is pressed on the root activity
* of one of its controlled tasks.
*/
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index 73b2fe1..f29eb39 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -184,19 +184,6 @@
}
/**
- * Set's the root task to launch new tasks into on a display. {@code null} means no launch
- * root and thus new tasks just end up directly on the display.
- */
- @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
- public void setLaunchRoot(int displayId, @NonNull WindowContainerToken root) {
- try {
- mTaskOrganizerController.setLaunchRoot(displayId, root);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
* Requests that the given task organizer is notified when back is pressed on the root activity
* of one of its controlled tasks.
*/
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index eba4fd2..6bc3110 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -30,6 +30,7 @@
import android.view.SurfaceControl;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -263,8 +264,9 @@
@NonNull
public WindowContainerTransaction reparent(@NonNull WindowContainerToken child,
@Nullable WindowContainerToken parent, boolean onTop) {
- mHierarchyOps.add(new HierarchyOp(child.asBinder(),
- parent == null ? null : parent.asBinder(), onTop));
+ mHierarchyOps.add(HierarchyOp.createForReparent(child.asBinder(),
+ parent == null ? null : parent.asBinder(),
+ onTop));
return this;
}
@@ -276,7 +278,47 @@
*/
@NonNull
public WindowContainerTransaction reorder(@NonNull WindowContainerToken child, boolean onTop) {
- mHierarchyOps.add(new HierarchyOp(child.asBinder(), onTop));
+ mHierarchyOps.add(HierarchyOp.createForReorder(child.asBinder(), onTop));
+ return this;
+ }
+
+ /**
+ * Reparent's all children tasks of {@param currentParent} in the specified
+ * {@param windowingMode} and {@param activityType} to {@param newParent} in their current
+ * z-order.
+ *
+ * @param currentParent of the tasks to perform the operation no.
+ * {@code null} will perform the operation on the display.
+ * @param newParent for the tasks. {@code null} will perform the operation on the display.
+ * @param windowingModes of the tasks to reparent.
+ * @param activityTypes of the tasks to reparent.
+ * @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to
+ * the bottom.
+ */
+ @NonNull
+ public WindowContainerTransaction reparentTasks(@Nullable WindowContainerToken currentParent,
+ @Nullable WindowContainerToken newParent, @Nullable int[] windowingModes,
+ @Nullable int[] activityTypes, boolean onTop) {
+ mHierarchyOps.add(HierarchyOp.createForChildrenTasksReparent(
+ currentParent != null ? currentParent.asBinder() : null,
+ newParent != null ? newParent.asBinder() : null,
+ windowingModes,
+ activityTypes,
+ onTop));
+ return this;
+ }
+
+ /**
+ * Sets whether a container should be the launch root for the specified windowing mode and
+ * activity type. This currently only applies to Task containers created by organizer.
+ */
+ @NonNull
+ public WindowContainerTransaction setLaunchRoot(@NonNull WindowContainerToken container,
+ @Nullable int[] windowingModes, @Nullable int[] activityTypes) {
+ mHierarchyOps.add(HierarchyOp.createForSetLaunchRoot(
+ container.asBinder(),
+ windowingModes,
+ activityTypes));
return this;
}
@@ -363,6 +405,7 @@
private boolean mFocusable = true;
private boolean mHidden = false;
private boolean mIgnoreOrientationRequest = false;
+
private int mChangeMask = 0;
private @ActivityInfo.Config int mConfigSetMask = 0;
private @WindowConfiguration.WindowConfig int mWindowSetMask = 0;
@@ -595,6 +638,14 @@
* @hide
*/
public static class HierarchyOp implements Parcelable {
+ public static final int HIERARCHY_OP_TYPE_REPARENT = 0;
+ public static final int HIERARCHY_OP_TYPE_REORDER = 1;
+ public static final int HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT = 2;
+ public static final int HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT = 3;
+
+ private final int mType;
+
+ // Container we are performing the operation on.
private final IBinder mContainer;
// If this is same as mContainer, then only change position, don't reparent.
@@ -603,32 +654,68 @@
// Moves/reparents to top of parent when {@code true}, otherwise moves/reparents to bottom.
private final boolean mToTop;
- public HierarchyOp(@NonNull IBinder container, @Nullable IBinder reparent, boolean toTop) {
- mContainer = container;
- mReparent = reparent;
- mToTop = toTop;
+ final private int[] mWindowingModes;
+ final private int[] mActivityTypes;
+
+ public static HierarchyOp createForReparent(
+ @NonNull IBinder container, @Nullable IBinder reparent, boolean toTop) {
+ return new HierarchyOp(HIERARCHY_OP_TYPE_REPARENT,
+ container, reparent, null, null, toTop);
}
- public HierarchyOp(@NonNull IBinder container, boolean toTop) {
+ public static HierarchyOp createForReorder(@NonNull IBinder container, boolean toTop) {
+ return new HierarchyOp(HIERARCHY_OP_TYPE_REORDER,
+ container, container, null, null, toTop);
+ }
+
+ public static HierarchyOp createForChildrenTasksReparent(IBinder currentParent,
+ IBinder newParent, int[] windowingModes, int[] activityTypes, boolean onTop) {
+ return new HierarchyOp(HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT,
+ currentParent, newParent, windowingModes, activityTypes, onTop);
+ }
+
+ public static HierarchyOp createForSetLaunchRoot(IBinder container,
+ int[] windowingModes, int[] activityTypes) {
+ return new HierarchyOp(HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT,
+ container, null, windowingModes, activityTypes, false);
+ }
+
+ private HierarchyOp(int type, @NonNull IBinder container, @Nullable IBinder reparent,
+ int[] windowingModes, int[] activityTypes, boolean toTop) {
+ mType = type;
mContainer = container;
- mReparent = container;
+ mReparent = reparent;
+ mWindowingModes = windowingModes != null ?
+ Arrays.copyOf(windowingModes, windowingModes.length) : null;
+ mActivityTypes = activityTypes != null ?
+ Arrays.copyOf(activityTypes, activityTypes.length) : null;
mToTop = toTop;
}
public HierarchyOp(@NonNull HierarchyOp copy) {
+ mType = copy.mType;
mContainer = copy.mContainer;
mReparent = copy.mReparent;
mToTop = copy.mToTop;
+ mWindowingModes = copy.mWindowingModes;
+ mActivityTypes = copy.mActivityTypes;
}
protected HierarchyOp(Parcel in) {
+ mType = in.readInt();
mContainer = in.readStrongBinder();
mReparent = in.readStrongBinder();
mToTop = in.readBoolean();
+ mWindowingModes = in.createIntArray();
+ mActivityTypes = in.createIntArray();
+ }
+
+ public int getType() {
+ return mType;
}
public boolean isReparent() {
- return mContainer != mReparent;
+ return mType == HIERARCHY_OP_TYPE_REPARENT;
}
@Nullable
@@ -645,21 +732,45 @@
return mToTop;
}
+ public int[] getWindowingModes() {
+ return mWindowingModes;
+ }
+
+ public int[] getActivityTypes() {
+ return mActivityTypes;
+ }
+
@Override
public String toString() {
- if (isReparent()) {
- return "{reparent: " + mContainer + " to " + (mToTop ? "top of " : "bottom of ")
- + mReparent + "}";
- } else {
- return "{reorder: " + mContainer + " to " + (mToTop ? "top" : "bottom") + "}";
+ switch (mType) {
+ case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT:
+ return "{ChildrenTasksReparent: from=" + mContainer + " to=" + mReparent
+ + " mToTop=" + mToTop + " mWindowingMode=" + mWindowingModes
+ + " mActivityType=" + mActivityTypes + "}";
+ case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT:
+ return "{SetLaunchRoot: container=" + mContainer
+ + " mWindowingMode=" + mWindowingModes
+ + " mActivityType=" + mActivityTypes + "}";
+ case HIERARCHY_OP_TYPE_REPARENT:
+ return "{reparent: " + mContainer + " to " + (mToTop ? "top of " : "bottom of ")
+ + mReparent + "}";
+ case HIERARCHY_OP_TYPE_REORDER:
+ return "{reorder: " + mContainer + " to " + (mToTop ? "top" : "bottom") + "}";
+ default:
+ return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent
+ + " mToTop=" + mToTop + " mWindowingMode=" + mWindowingModes
+ + " mActivityType=" + mActivityTypes + "}";
}
}
@Override
public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mType);
dest.writeStrongBinder(mContainer);
dest.writeStrongBinder(mReparent);
dest.writeBoolean(mToTop);
+ dest.writeIntArray(mWindowingModes);
+ dest.writeIntArray(mActivityTypes);
}
@Override
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index e064137..34e03af 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -431,19 +431,22 @@
}
private void maybeHideContentPreview() {
- if (!mAtLeastOneLoaded && mHideParentOnFail) {
- Log.i(TAG, "Hiding image preview area. Timed out waiting for preview to load"
- + " within " + mImageLoadTimeoutMillis + "ms.");
- collapseParentView();
- if (shouldShowTabs()) {
- hideStickyContentPreview();
- } else if (mChooserMultiProfilePagerAdapter.getCurrentRootAdapter() != null) {
- mChooserMultiProfilePagerAdapter.getCurrentRootAdapter().hideContentPreview();
+ if (!mAtLeastOneLoaded) {
+ if (mHideParentOnFail) {
+ Log.i(TAG, "Hiding image preview area. Timed out waiting for preview to load"
+ + " within " + mImageLoadTimeoutMillis + "ms.");
+ collapseParentView();
+ if (shouldShowTabs()) {
+ hideStickyContentPreview();
+ } else if (mChooserMultiProfilePagerAdapter.getCurrentRootAdapter() != null) {
+ mChooserMultiProfilePagerAdapter.getCurrentRootAdapter()
+ .hideContentPreview();
+ }
+ mHideParentOnFail = false;
}
- mHideParentOnFail = false;
+ mRemoveSharedElements = true;
+ startPostponedEnterTransition();
}
- mRemoveSharedElements = true;
- startPostponedEnterTransition();
}
private void collapseParentView() {
diff --git a/core/java/com/android/internal/compat/CompatibilityChangeInfo.java b/core/java/com/android/internal/compat/CompatibilityChangeInfo.java
index 670ca9f..03fe455 100644
--- a/core/java/com/android/internal/compat/CompatibilityChangeInfo.java
+++ b/core/java/com/android/internal/compat/CompatibilityChangeInfo.java
@@ -32,6 +32,7 @@
private final boolean mDisabled;
private final boolean mLoggingOnly;
private final @Nullable String mDescription;
+ private final boolean mOverridable;
public long getId() {
return mChangeId;
@@ -58,9 +59,13 @@
return mDescription;
}
+ public boolean getOverridable() {
+ return mOverridable;
+ }
+
public CompatibilityChangeInfo(
Long changeId, String name, int enableAfterTargetSdk, int enableSinceTargetSdk,
- boolean disabled, boolean loggingOnly, String description) {
+ boolean disabled, boolean loggingOnly, String description, boolean overridable) {
this.mChangeId = changeId;
this.mName = name;
if (enableAfterTargetSdk > 0) {
@@ -75,6 +80,7 @@
this.mDisabled = disabled;
this.mLoggingOnly = loggingOnly;
this.mDescription = description;
+ this.mOverridable = overridable;
}
public CompatibilityChangeInfo(CompatibilityChangeInfo other) {
@@ -84,6 +90,7 @@
this.mDisabled = other.mDisabled;
this.mLoggingOnly = other.mLoggingOnly;
this.mDescription = other.mDescription;
+ this.mOverridable = other.mOverridable;
}
private CompatibilityChangeInfo(Parcel in) {
@@ -93,6 +100,7 @@
mDisabled = in.readBoolean();
mLoggingOnly = in.readBoolean();
mDescription = in.readString();
+ mOverridable = in.readBoolean();
}
@Override
@@ -108,6 +116,7 @@
dest.writeBoolean(mDisabled);
dest.writeBoolean(mLoggingOnly);
dest.writeString(mDescription);
+ dest.writeBoolean(mOverridable);
}
@Override
@@ -126,6 +135,9 @@
if (getLoggingOnly()) {
sb.append("; loggingOnly");
}
+ if (getOverridable()) {
+ sb.append("; overridable");
+ }
return sb.append(")").toString();
}
@@ -143,8 +155,8 @@
&& this.mEnableSinceTargetSdk == that.mEnableSinceTargetSdk
&& this.mDisabled == that.mDisabled
&& this.mLoggingOnly == that.mLoggingOnly
- && this.mDescription.equals(that.mDescription);
-
+ && this.mDescription.equals(that.mDescription)
+ && this.mOverridable == that.mOverridable;
}
public static final Parcelable.Creator<CompatibilityChangeInfo> CREATOR =
diff --git a/core/java/com/android/internal/inputmethod/Completable.java b/core/java/com/android/internal/inputmethod/Completable.java
index b82ba81..d6a4663 100644
--- a/core/java/com/android/internal/inputmethod/Completable.java
+++ b/core/java/com/android/internal/inputmethod/Completable.java
@@ -117,8 +117,8 @@
}
/**
- * @return {@link true} if {@link #onComplete()} gets called and {@link #mState} is
- * {@link CompletionState#COMPLETED_WITH_VALUE} .
+ * @return {@code true} if {@link #onComplete()} gets called and {@link #mState} is
+ * {@link CompletionState#COMPLETED_WITH_VALUE}.
*/
@AnyThread
public boolean hasValue() {
@@ -232,13 +232,25 @@
}
/**
- * Blocks the calling thread until this object becomes ready to return the value.
+ * Blocks the calling thread until this object becomes ready to return the value, even if
+ * {@link InterruptedException} is thrown.
*/
@AnyThread
public void await() {
- try {
- mLatch.await();
- } catch (InterruptedException ignored) { }
+ boolean interrupted = false;
+ while (true) {
+ try {
+ mLatch.await();
+ break;
+ } catch (InterruptedException ignored) {
+ interrupted = true;
+ }
+ }
+
+ if (interrupted) {
+ // Try to preserve the interrupt bit on this thread.
+ Thread.currentThread().interrupt();
+ }
}
}
@@ -487,7 +499,7 @@
/**
* Await the result by the {@link Completable.Values}.
*
- * @return the result once {@link ValueBase#onComplete()}
+ * @return the result once {@link ValueBase#onComplete()}.
*/
@AnyThread
@Nullable
@@ -499,7 +511,7 @@
/**
* Await the int result by the {@link Completable.Int}.
*
- * @return the result once {@link ValueBase#onComplete()}
+ * @return the result once {@link ValueBase#onComplete()}.
*/
@AnyThread
public static int getIntResult(@NonNull Completable.Int value) {
diff --git a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
index 9c3bb76..6609ebe 100644
--- a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
+++ b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
@@ -16,7 +16,11 @@
package com.android.internal.os;
+import android.os.BatteryConsumer;
import android.os.BatteryStats;
+import android.os.BatteryUsageStats;
+import android.os.BatteryUsageStatsQuery;
+import android.os.SystemBatteryConsumer;
import android.os.UserHandle;
import android.util.SparseArray;
@@ -26,11 +30,30 @@
* Estimates power consumed by the ambient display
*/
public class AmbientDisplayPowerCalculator extends PowerCalculator {
-
- private final PowerProfile mPowerProfile;
+ private final UsageBasedPowerEstimator mPowerEstimator;
public AmbientDisplayPowerCalculator(PowerProfile powerProfile) {
- mPowerProfile = powerProfile;
+ mPowerEstimator = new UsageBasedPowerEstimator(
+ powerProfile.getAveragePower(PowerProfile.POWER_AMBIENT_DISPLAY));
+ }
+
+ /**
+ * Ambient display power is the additional power the screen takes while in ambient display/
+ * screen doze/always-on display (interchangeable terms) mode.
+ */
+ @Override
+ public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
+ long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
+ SparseArray<UserHandle> asUsers) {
+ final long durationMs = calculateDuration(batteryStats, rawRealtimeUs,
+ BatteryStats.STATS_SINCE_CHARGED);
+ final double powerMah = mPowerEstimator.calculatePower(durationMs);
+ if (powerMah > 0) {
+ builder.getOrCreateSystemBatteryConsumerBuilder(
+ SystemBatteryConsumer.DRAIN_TYPE_AMBIENT_DISPLAY)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE, powerMah)
+ .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE, durationMs);
+ }
}
/**
@@ -42,16 +65,18 @@
@Override
public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
-
- long ambientDisplayMs = batteryStats.getScreenDozeTime(rawRealtimeUs, statsType) / 1000;
- double power = mPowerProfile.getAveragePower(PowerProfile.POWER_AMBIENT_DISPLAY)
- * ambientDisplayMs / (60 * 60 * 1000);
- if (power > 0) {
+ final long durationMs = calculateDuration(batteryStats, rawRealtimeUs, statsType);
+ final double powerMah = mPowerEstimator.calculatePower(durationMs);
+ if (powerMah > 0) {
BatterySipper bs = new BatterySipper(BatterySipper.DrainType.AMBIENT_DISPLAY, null, 0);
- bs.usagePowerMah = power;
- bs.usageTimeMs = ambientDisplayMs;
+ bs.usagePowerMah = powerMah;
+ bs.usageTimeMs = durationMs;
bs.sumPower();
sippers.add(bs);
}
}
+
+ private long calculateDuration(BatteryStats batteryStats, long rawRealtimeUs, int statsType) {
+ return batteryStats.getScreenDozeTime(rawRealtimeUs, statsType) / 1000;
+ }
}
diff --git a/core/java/com/android/internal/os/AudioPowerCalculator.java b/core/java/com/android/internal/os/AudioPowerCalculator.java
new file mode 100644
index 0000000..79b331d
--- /dev/null
+++ b/core/java/com/android/internal/os/AudioPowerCalculator.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.os;
+
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.BatteryUsageStatsQuery;
+import android.os.UidBatteryConsumer;
+
+/**
+ * A {@link PowerCalculator} to calculate power consumed by audio hardware.
+ *
+ * Also see {@link PowerProfile#POWER_AUDIO}.
+ */
+public class AudioPowerCalculator extends PowerCalculator {
+ // Calculate audio power usage, an estimate based on the average power routed to different
+ // components like speaker, bluetooth, usb-c, earphone, etc.
+ // TODO(b/175344313): improve the model by taking into account different audio routes
+ private final UsageBasedPowerEstimator mPowerEstimator;
+
+ public AudioPowerCalculator(PowerProfile powerProfile) {
+ mPowerEstimator = new UsageBasedPowerEstimator(
+ powerProfile.getAveragePower(PowerProfile.POWER_AUDIO));
+ }
+
+ @Override
+ protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
+ long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+ final long durationMs = mPowerEstimator.calculateDuration(u.getAudioTurnedOnTimer(),
+ rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED);
+ final double powerMah = mPowerEstimator.calculatePower(durationMs);
+ app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_AUDIO, durationMs)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_AUDIO, powerMah);
+ }
+}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 93dff9f..33aa190 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -873,7 +873,9 @@
protected StopwatchTimer mScreenDozeTimer;
int mScreenBrightnessBin = -1;
- final StopwatchTimer[] mScreenBrightnessTimer = new StopwatchTimer[NUM_SCREEN_BRIGHTNESS_BINS];
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ protected final StopwatchTimer[] mScreenBrightnessTimer =
+ new StopwatchTimer[NUM_SCREEN_BRIGHTNESS_BINS];
boolean mPretendScreenOff;
diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
index 9904d30..e5d64a0 100644
--- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
+++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
@@ -66,7 +66,8 @@
mContext.getSystemService(SensorManager.class)));
mPowerCalculators.add(new CameraPowerCalculator(mPowerProfile));
mPowerCalculators.add(new FlashlightPowerCalculator(mPowerProfile));
- mPowerCalculators.add(new MediaPowerCalculator(mPowerProfile));
+ mPowerCalculators.add(new AudioPowerCalculator(mPowerProfile));
+ mPowerCalculators.add(new VideoPowerCalculator(mPowerProfile));
mPowerCalculators.add(new PhonePowerCalculator(mPowerProfile));
mPowerCalculators.add(new ScreenPowerCalculator(mPowerProfile));
mPowerCalculators.add(new AmbientDisplayPowerCalculator(mPowerProfile));
diff --git a/core/java/com/android/internal/os/BluetoothPowerCalculator.java b/core/java/com/android/internal/os/BluetoothPowerCalculator.java
index f5690e0..4c3b950 100644
--- a/core/java/com/android/internal/os/BluetoothPowerCalculator.java
+++ b/core/java/com/android/internal/os/BluetoothPowerCalculator.java
@@ -29,8 +29,8 @@
import java.util.List;
public class BluetoothPowerCalculator extends PowerCalculator {
- private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
private static final String TAG = "BluetoothPowerCalc";
+ private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
private final double mIdleMa;
private final double mRxMa;
private final double mTxMa;
@@ -41,11 +41,6 @@
public double powerMah;
}
- // Objects used for passing calculation results. Fields are used to avoid allocations.
- private final PowerAndDuration mUidPowerAndDuration = new PowerAndDuration();
- private final PowerAndDuration mTotalPowerAndDuration = new PowerAndDuration();
- private final PowerAndDuration mSystemPowerAndDuration = new PowerAndDuration();
-
public BluetoothPowerCalculator(PowerProfile profile) {
mIdleMa = profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_IDLE);
mRxMa = profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_RX);
@@ -61,8 +56,7 @@
return;
}
- mTotalPowerAndDuration.durationMs = 0;
- mTotalPowerAndDuration.powerMah = 0;
+ final PowerAndDuration total = new PowerAndDuration();
SystemBatteryConsumer.Builder systemBatteryConsumerBuilder =
builder.getOrCreateSystemBatteryConsumerBuilder(
@@ -72,24 +66,25 @@
builder.getUidBatteryConsumerBuilders();
for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
- calculateApp(app);
+ calculateApp(app, total);
if (app.getUid() == Process.BLUETOOTH_UID) {
app.setSystemComponent(true);
systemBatteryConsumerBuilder.addUidBatteryConsumer(app);
}
}
- final BatteryStats.ControllerActivityCounter counter =
+ final BatteryStats.ControllerActivityCounter activityCounter =
batteryStats.getBluetoothControllerActivity();
-
- calculatePowerAndDuration(counter, mSystemPowerAndDuration);
+ final long systemDurationMs = calculateDuration(activityCounter);
+ final double systemPowerMah = calculatePower(activityCounter);
// Subtract what the apps used, but clamp to 0.
- final long systemComponentDurationMs = Math.max(0,
- mSystemPowerAndDuration.durationMs - mTotalPowerAndDuration.durationMs);
- final double systemComponentPowerMah = Math.max(0,
- mSystemPowerAndDuration.powerMah - mTotalPowerAndDuration.powerMah);
-
+ final long systemComponentDurationMs = Math.max(0, systemDurationMs - total.durationMs);
+ final double systemComponentPowerMah = Math.max(0, systemPowerMah - total.powerMah);
+ if (DEBUG && systemComponentPowerMah != 0) {
+ Log.d(TAG, "Bluetooth active: time=" + (systemComponentDurationMs)
+ + " power=" + formatCharge(systemComponentPowerMah));
+ }
systemBatteryConsumerBuilder
.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_BLUETOOTH,
systemComponentDurationMs)
@@ -97,17 +92,17 @@
systemComponentPowerMah);
}
- private void calculateApp(UidBatteryConsumer.Builder app) {
- calculatePowerAndDuration(app.getBatteryStatsUid().getBluetoothControllerActivity(),
- mUidPowerAndDuration);
+ private void calculateApp(UidBatteryConsumer.Builder app, PowerAndDuration total) {
+ final BatteryStats.ControllerActivityCounter activityCounter =
+ app.getBatteryStatsUid().getBluetoothControllerActivity();
+ final long durationMs = calculateDuration(activityCounter);
+ final double powerMah = calculatePower(activityCounter);
- app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_BLUETOOTH,
- mUidPowerAndDuration.durationMs)
- .setConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH,
- mUidPowerAndDuration.powerMah);
+ app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_BLUETOOTH, durationMs)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, powerMah);
- mTotalPowerAndDuration.powerMah += mUidPowerAndDuration.powerMah;
- mTotalPowerAndDuration.durationMs += mUidPowerAndDuration.durationMs;
+ total.durationMs += durationMs;
+ total.powerMah += powerMah;
}
@Override
@@ -117,20 +112,24 @@
return;
}
- mTotalPowerAndDuration.durationMs = 0;
- mTotalPowerAndDuration.powerMah = 0;
+ PowerAndDuration total = new PowerAndDuration();
- super.calculate(sippers, batteryStats, rawRealtimeUs, rawUptimeUs, statsType, asUsers);
+ for (int i = sippers.size() - 1; i >= 0; i--) {
+ final BatterySipper app = sippers.get(i);
+ if (app.drainType == BatterySipper.DrainType.APP) {
+ calculateApp(app, app.uidObj, statsType, total);
+ }
+ }
BatterySipper bs = new BatterySipper(BatterySipper.DrainType.BLUETOOTH, null, 0);
- calculatePowerAndDuration(batteryStats.getBluetoothControllerActivity(),
- mSystemPowerAndDuration);
+ final BatteryStats.ControllerActivityCounter activityCounter =
+ batteryStats.getBluetoothControllerActivity();
+ final double systemPowerMah = calculatePower(activityCounter);
+ final long systemDurationMs = calculateDuration(activityCounter);
// Subtract what the apps used, but clamp to 0.
- double powerMah =
- Math.max(0, mSystemPowerAndDuration.powerMah - mTotalPowerAndDuration.powerMah);
- final long durationMs =
- Math.max(0, mSystemPowerAndDuration.durationMs - mTotalPowerAndDuration.durationMs);
+ final double powerMah = Math.max(0, systemPowerMah - total.powerMah);
+ final long durationMs = Math.max(0, systemDurationMs - total.durationMs);
if (DEBUG && powerMah != 0) {
Log.d(TAG, "Bluetooth active: time=" + (durationMs)
+ " power=" + formatCharge(powerMah));
@@ -152,27 +151,43 @@
}
}
- @Override
- protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
- long rawUptimeUs, int statsType) {
+ private void calculateApp(BatterySipper app, BatteryStats.Uid u, int statsType,
+ PowerAndDuration total) {
+ final BatteryStats.ControllerActivityCounter activityCounter =
+ u.getBluetoothControllerActivity();
+ final long durationMs = calculateDuration(activityCounter);
+ final double powerMah = calculatePower(activityCounter);
- calculatePowerAndDuration(u.getBluetoothControllerActivity(), mUidPowerAndDuration);
-
- app.bluetoothPowerMah = mUidPowerAndDuration.powerMah;
- app.bluetoothRunningTimeMs = mUidPowerAndDuration.durationMs;
+ app.bluetoothRunningTimeMs = durationMs;
+ app.bluetoothPowerMah = powerMah;
app.btRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_BT_RX_DATA, statsType);
app.btTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_BT_TX_DATA, statsType);
- mTotalPowerAndDuration.powerMah += mUidPowerAndDuration.powerMah;
- mTotalPowerAndDuration.durationMs += mUidPowerAndDuration.durationMs;
+ total.durationMs += durationMs;
+ total.powerMah += powerMah;
}
- private void calculatePowerAndDuration(BatteryStats.ControllerActivityCounter counter,
- PowerAndDuration powerAndDuration) {
+ private long calculateDuration(BatteryStats.ControllerActivityCounter counter) {
if (counter == null) {
- powerAndDuration.durationMs = 0;
- powerAndDuration.powerMah = 0;
- return;
+ return 0;
+ }
+
+ return counter.getIdleTimeCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED)
+ + counter.getRxTimeCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED)
+ + counter.getTxTimeCounters()[0].getCountLocked(BatteryStats.STATS_SINCE_CHARGED);
+ }
+
+ private double calculatePower(BatteryStats.ControllerActivityCounter counter) {
+ if (counter == null) {
+ return 0;
+ }
+
+ final double powerMah =
+ counter.getPowerCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED)
+ / (double) (1000 * 60 * 60);
+
+ if (powerMah != 0) {
+ return powerMah;
}
final long idleTimeMs =
@@ -181,17 +196,7 @@
counter.getRxTimeCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED);
final long txTimeMs =
counter.getTxTimeCounters()[0].getCountLocked(BatteryStats.STATS_SINCE_CHARGED);
- final long totalTimeMs = idleTimeMs + txTimeMs + rxTimeMs;
- double powerMah =
- counter.getPowerCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED)
- / (double) (1000 * 60 * 60);
-
- if (powerMah == 0) {
- powerMah = ((idleTimeMs * mIdleMa) + (rxTimeMs * mRxMa) + (txTimeMs * mTxMa))
- / (1000 * 60 * 60);
- }
-
- powerAndDuration.durationMs = totalTimeMs;
- powerAndDuration.powerMah = powerMah;
+ return ((idleTimeMs * mIdleMa) + (rxTimeMs * mRxMa) + (txTimeMs * mTxMa))
+ / (1000 * 60 * 60);
}
}
diff --git a/core/java/com/android/internal/os/CameraPowerCalculator.java b/core/java/com/android/internal/os/CameraPowerCalculator.java
index 0365d9e..6f8e927 100644
--- a/core/java/com/android/internal/os/CameraPowerCalculator.java
+++ b/core/java/com/android/internal/os/CameraPowerCalculator.java
@@ -15,7 +15,10 @@
*/
package com.android.internal.os;
+import android.os.BatteryConsumer;
import android.os.BatteryStats;
+import android.os.BatteryUsageStatsQuery;
+import android.os.UidBatteryConsumer;
/**
* Power calculator for the camera subsystem, excluding the flashlight.
@@ -23,26 +26,33 @@
* Note: Power draw for the flash unit should be included in the FlashlightPowerCalculator.
*/
public class CameraPowerCalculator extends PowerCalculator {
- private final double mCameraPowerOnAvg;
+ // Calculate camera power usage. Right now, this is a (very) rough estimate based on the
+ // average power usage for a typical camera application.
+ private final UsageBasedPowerEstimator mPowerEstimator;
public CameraPowerCalculator(PowerProfile profile) {
- mCameraPowerOnAvg = profile.getAveragePower(PowerProfile.POWER_CAMERA);
+ mPowerEstimator = new UsageBasedPowerEstimator(
+ profile.getAveragePower(PowerProfile.POWER_CAMERA));
+ }
+
+ @Override
+ protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
+ long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+ final long durationMs =
+ mPowerEstimator.calculateDuration(u.getCameraTurnedOnTimer(), rawRealtimeUs,
+ BatteryStats.STATS_SINCE_CHARGED);
+ final double powerMah = mPowerEstimator.calculatePower(durationMs);
+ app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CAMERA, durationMs)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA, powerMah);
}
@Override
protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
- long rawUptimeUs, int statsType) {
-
- // Calculate camera power usage. Right now, this is a (very) rough estimate based on the
- // average power usage for a typical camera application.
- final BatteryStats.Timer timer = u.getCameraTurnedOnTimer();
- if (timer != null) {
- final long totalTime = timer.getTotalTimeLocked(rawRealtimeUs, statsType) / 1000;
- app.cameraTimeMs = totalTime;
- app.cameraPowerMah = (totalTime * mCameraPowerOnAvg) / (1000*60*60);
- } else {
- app.cameraTimeMs = 0;
- app.cameraPowerMah = 0;
- }
+ long rawUptimeUs, int statsType) {
+ final long durationMs = mPowerEstimator.calculateDuration(u.getCameraTurnedOnTimer(),
+ rawRealtimeUs, statsType);
+ final double powerMah = mPowerEstimator.calculatePower(durationMs);
+ app.cameraTimeMs = durationMs;
+ app.cameraPowerMah = powerMah;
}
}
diff --git a/core/java/com/android/internal/os/FlashlightPowerCalculator.java b/core/java/com/android/internal/os/FlashlightPowerCalculator.java
index 330feef..6c29a91 100644
--- a/core/java/com/android/internal/os/FlashlightPowerCalculator.java
+++ b/core/java/com/android/internal/os/FlashlightPowerCalculator.java
@@ -15,32 +15,41 @@
*/
package com.android.internal.os;
+import android.os.BatteryConsumer;
import android.os.BatteryStats;
+import android.os.BatteryUsageStatsQuery;
+import android.os.UidBatteryConsumer;
/**
* Power calculator for the flashlight.
*/
public class FlashlightPowerCalculator extends PowerCalculator {
- private final double mFlashlightPowerOnAvg;
+ // Calculate flashlight power usage. Right now, this is based on the average power draw
+ // of the flash unit when kept on over a short period of time.
+ private final UsageBasedPowerEstimator mPowerEstimator;
public FlashlightPowerCalculator(PowerProfile profile) {
- mFlashlightPowerOnAvg = profile.getAveragePower(PowerProfile.POWER_FLASHLIGHT);
+ mPowerEstimator = new UsageBasedPowerEstimator(
+ profile.getAveragePower(PowerProfile.POWER_FLASHLIGHT));
+ }
+
+ @Override
+ protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
+ long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+ final long durationMs = mPowerEstimator.calculateDuration(u.getFlashlightTurnedOnTimer(),
+ rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED);
+ final double powerMah = mPowerEstimator.calculatePower(durationMs);
+ app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_FLASHLIGHT, durationMs)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT, powerMah);
}
@Override
protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
- long rawUptimeUs, int statsType) {
-
- // Calculate flashlight power usage. Right now, this is based on the average power draw
- // of the flash unit when kept on over a short period of time.
- final BatteryStats.Timer timer = u.getFlashlightTurnedOnTimer();
- if (timer != null) {
- final long totalTime = timer.getTotalTimeLocked(rawRealtimeUs, statsType) / 1000;
- app.flashlightTimeMs = totalTime;
- app.flashlightPowerMah = (totalTime * mFlashlightPowerOnAvg) / (1000*60*60);
- } else {
- app.flashlightTimeMs = 0;
- app.flashlightPowerMah = 0;
- }
+ long rawUptimeUs, int statsType) {
+ final long durationMs = mPowerEstimator.calculateDuration(u.getFlashlightTurnedOnTimer(),
+ rawRealtimeUs, statsType);
+ final double powerMah = mPowerEstimator.calculatePower(durationMs);
+ app.flashlightTimeMs = durationMs;
+ app.flashlightPowerMah = powerMah;
}
}
diff --git a/core/java/com/android/internal/os/IdlePowerCalculator.java b/core/java/com/android/internal/os/IdlePowerCalculator.java
index 44ad344..dcc8a15 100644
--- a/core/java/com/android/internal/os/IdlePowerCalculator.java
+++ b/core/java/com/android/internal/os/IdlePowerCalculator.java
@@ -16,7 +16,11 @@
package com.android.internal.os;
+import android.os.BatteryConsumer;
import android.os.BatteryStats;
+import android.os.BatteryUsageStats;
+import android.os.BatteryUsageStatsQuery;
+import android.os.SystemBatteryConsumer;
import android.os.UserHandle;
import android.util.Log;
import android.util.SparseArray;
@@ -29,46 +33,70 @@
public class IdlePowerCalculator extends PowerCalculator {
private static final String TAG = "IdlePowerCalculator";
private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
- private final PowerProfile mPowerProfile;
+ private final double mAveragePowerCpuSuspendMahPerUs;
+ private final double mAveragePowerCpuIdleMahPerUs;
+ public long mDurationMs;
+ public double mPowerMah;
public IdlePowerCalculator(PowerProfile powerProfile) {
- mPowerProfile = powerProfile;
+ mAveragePowerCpuSuspendMahPerUs =
+ powerProfile.getAveragePower(PowerProfile.POWER_CPU_SUSPEND)
+ / (60 * 60 * 1_000_000.0);
+ mAveragePowerCpuIdleMahPerUs =
+ powerProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE)
+ / (60 * 60 * 1_000_000.0);
}
- /**
- * Calculate the baseline power usage for the device when it is in suspend and idle.
- * The device is drawing POWER_CPU_SUSPEND power at its lowest power state.
- * The device is drawing POWER_CPU_SUSPEND + POWER_CPU_IDLE power when a wakelock is held.
- */
+ @Override
+ public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
+ long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
+ SparseArray<UserHandle> asUsers) {
+ calculatePowerAndDuration(batteryStats, rawRealtimeUs, rawUptimeUs,
+ BatteryStats.STATS_SINCE_CHARGED);
+ if (mPowerMah != 0) {
+ builder.getOrCreateSystemBatteryConsumerBuilder(SystemBatteryConsumer.DRAIN_TYPE_IDLE)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE, mPowerMah)
+ .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE, mDurationMs);
+ }
+ }
+
@Override
public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
- long batteryUptimeUs = batteryStats.computeBatteryUptime(rawUptimeUs, statsType);
- long batteryRealtimeUs = batteryStats.computeBatteryRealtime(rawRealtimeUs, statsType);
+ calculatePowerAndDuration(batteryStats, rawRealtimeUs, rawUptimeUs, statsType);
+ if (mPowerMah != 0) {
+ BatterySipper bs = new BatterySipper(BatterySipper.DrainType.IDLE, null, 0);
+ bs.usagePowerMah = mPowerMah;
+ bs.usageTimeMs = mDurationMs;
+ bs.sumPower();
+ sippers.add(bs);
+ }
+ }
+
+ /**
+ * Calculates the baseline power usage for the device when it is in suspend and idle.
+ * The device is drawing POWER_CPU_SUSPEND power at its lowest power state.
+ * The device is drawing POWER_CPU_SUSPEND + POWER_CPU_IDLE power when a wakelock is held.
+ */
+ private void calculatePowerAndDuration(BatteryStats batteryStats, long rawRealtimeUs,
+ long rawUptimeUs, int statsType) {
+ long batteryRealtimeUs = batteryStats.computeBatteryRealtime(rawRealtimeUs, statsType);
+ long batteryUptimeUs = batteryStats.computeBatteryUptime(rawUptimeUs, statsType);
if (DEBUG) {
Log.d(TAG, "Battery type time: realtime=" + (batteryRealtimeUs / 1000) + " uptime="
+ (batteryUptimeUs / 1000));
}
- final double suspendPowerMaMs = (batteryRealtimeUs / 1000)
- * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_SUSPEND);
- final double idlePowerMaMs = (batteryUptimeUs / 1000)
- * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE);
- final double totalPowerMah = (suspendPowerMaMs + idlePowerMaMs) / (60 * 60 * 1000);
- if (DEBUG && totalPowerMah != 0) {
+ final double suspendPowerMah = batteryRealtimeUs * mAveragePowerCpuSuspendMahPerUs;
+ final double idlePowerMah = batteryUptimeUs * mAveragePowerCpuIdleMahPerUs;
+ mPowerMah = suspendPowerMah + idlePowerMah;
+ if (DEBUG && mPowerMah != 0) {
Log.d(TAG, "Suspend: time=" + (batteryRealtimeUs / 1000)
- + " power=" + formatCharge(suspendPowerMaMs / (60 * 60 * 1000)));
+ + " power=" + formatCharge(suspendPowerMah));
Log.d(TAG, "Idle: time=" + (batteryUptimeUs / 1000)
- + " power=" + formatCharge(idlePowerMaMs / (60 * 60 * 1000)));
+ + " power=" + formatCharge(idlePowerMah));
}
-
- if (totalPowerMah != 0) {
- BatterySipper bs = new BatterySipper(BatterySipper.DrainType.IDLE, null, 0);
- bs.usagePowerMah = totalPowerMah;
- bs.usageTimeMs = batteryRealtimeUs / 1000;
- bs.sumPower();
- sippers.add(bs);
- }
+ mDurationMs = batteryRealtimeUs / 1000;
}
}
diff --git a/core/java/com/android/internal/os/MemoryPowerCalculator.java b/core/java/com/android/internal/os/MemoryPowerCalculator.java
index 10d9b65..df46058 100644
--- a/core/java/com/android/internal/os/MemoryPowerCalculator.java
+++ b/core/java/com/android/internal/os/MemoryPowerCalculator.java
@@ -1,64 +1,75 @@
package com.android.internal.os;
+import android.os.BatteryConsumer;
import android.os.BatteryStats;
+import android.os.BatteryUsageStats;
+import android.os.BatteryUsageStatsQuery;
+import android.os.SystemBatteryConsumer;
import android.os.UserHandle;
-import android.util.Log;
import android.util.LongSparseArray;
import android.util.SparseArray;
import java.util.List;
public class MemoryPowerCalculator extends PowerCalculator {
-
public static final String TAG = "MemoryPowerCalculator";
- private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
- private final double[] powerAverages;
+ private final UsageBasedPowerEstimator[] mPowerEstimators;
public MemoryPowerCalculator(PowerProfile profile) {
int numBuckets = profile.getNumElements(PowerProfile.POWER_MEMORY);
- powerAverages = new double[numBuckets];
+ mPowerEstimators = new UsageBasedPowerEstimator[numBuckets];
for (int i = 0; i < numBuckets; i++) {
- powerAverages[i] = profile.getAveragePower(PowerProfile.POWER_MEMORY, i);
- if (powerAverages[i] == 0 && DEBUG) {
- Log.d(TAG, "Problem with PowerProfile. Received 0 value in MemoryPowerCalculator");
- }
+ mPowerEstimators[i] = new UsageBasedPowerEstimator(
+ profile.getAveragePower(PowerProfile.POWER_MEMORY, i));
}
}
@Override
+ public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
+ long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
+ SparseArray<UserHandle> asUsers) {
+ final long durationMs = calculateDuration(batteryStats, rawRealtimeUs,
+ BatteryStats.STATS_SINCE_CHARGED);
+ final double powerMah = calculatePower(batteryStats, rawRealtimeUs,
+ BatteryStats.STATS_SINCE_CHARGED);
+ builder.getOrCreateSystemBatteryConsumerBuilder(SystemBatteryConsumer.DRAIN_TYPE_MEMORY)
+ .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE, durationMs)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE, powerMah);
+ }
+
+ @Override
public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
+ final long durationMs = calculateDuration(batteryStats, rawRealtimeUs, statsType);
+ final double powerMah = calculatePower(batteryStats, rawRealtimeUs, statsType);
BatterySipper memory = new BatterySipper(BatterySipper.DrainType.MEMORY, null, 0);
- calculateRemaining(memory, batteryStats, rawRealtimeUs, rawUptimeUs, statsType);
+ memory.usageTimeMs = durationMs;
+ memory.usagePowerMah = powerMah;
memory.sumPower();
if (memory.totalPowerMah > 0) {
sippers.add(memory);
}
}
- private void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
- long rawUptimeUs, int statsType) {
- double totalMah = 0;
- long totalTimeMs = 0;
- LongSparseArray<? extends BatteryStats.Timer> timers = stats.getKernelMemoryStats();
- for (int i = 0; i < timers.size() && i < powerAverages.length; i++) {
- double mAatRail = powerAverages[(int) timers.keyAt(i)];
- long timeMs = timers.valueAt(i).getTotalTimeLocked(rawRealtimeUs, statsType);
- double mAm = (mAatRail * timeMs) / (1000*60);
- if(DEBUG) {
- Log.d(TAG, "Calculating mAh for bucket " + timers.keyAt(i) + " while unplugged");
- Log.d(TAG, "Converted power profile number from "
- + powerAverages[(int) timers.keyAt(i)] + " into " + mAatRail);
- Log.d(TAG, "Calculated mAm " + mAm);
- }
- totalMah += mAm/60;
- totalTimeMs += timeMs;
+ private long calculateDuration(BatteryStats batteryStats, long rawRealtimeUs, int statsType) {
+ long usageDurationMs = 0;
+ LongSparseArray<? extends BatteryStats.Timer> timers = batteryStats.getKernelMemoryStats();
+ for (int i = 0; i < timers.size() && i < mPowerEstimators.length; i++) {
+ usageDurationMs += mPowerEstimators[i].calculateDuration(timers.valueAt(i),
+ rawRealtimeUs, statsType);
}
- app.usagePowerMah = totalMah;
- app.usageTimeMs = totalTimeMs;
- if (DEBUG) {
- Log.d(TAG, String.format("Calculated total mAh for memory %f while unplugged %d ",
- totalMah, totalTimeMs));
+ return usageDurationMs;
+ }
+
+ private double calculatePower(BatteryStats batteryStats, long rawRealtimeUs, int statsType) {
+ double powerMah = 0;
+ LongSparseArray<? extends BatteryStats.Timer> timers = batteryStats.getKernelMemoryStats();
+ for (int i = 0; i < timers.size() && i < mPowerEstimators.length; i++) {
+ UsageBasedPowerEstimator estimator = mPowerEstimators[(int) timers.keyAt(i)];
+ final long usageDurationMs =
+ estimator.calculateDuration(timers.valueAt(i), rawRealtimeUs, statsType);
+ powerMah += estimator.calculatePower(usageDurationMs);
}
+ return powerMah;
}
}
diff --git a/core/java/com/android/internal/os/OWNERS b/core/java/com/android/internal/os/OWNERS
index 8f78b2a..1b07aa0 100644
--- a/core/java/com/android/internal/os/OWNERS
+++ b/core/java/com/android/internal/os/OWNERS
@@ -6,3 +6,5 @@
per-file BatteryStats* = file:/BATTERY_STATS_OWNERS
per-file BatteryUsageStats* = file:/BATTERY_STATS_OWNERS
per-file *PowerCalculator* = file:/BATTERY_STATS_OWNERS
+per-file *PowerEstimator* = file:/BATTERY_STATS_OWNERS
+
diff --git a/core/java/com/android/internal/os/PhonePowerCalculator.java b/core/java/com/android/internal/os/PhonePowerCalculator.java
index 992c487..6ab8c90 100644
--- a/core/java/com/android/internal/os/PhonePowerCalculator.java
+++ b/core/java/com/android/internal/os/PhonePowerCalculator.java
@@ -30,20 +30,20 @@
* Estimates power consumed by telephony.
*/
public class PhonePowerCalculator extends PowerCalculator {
- private final PowerProfile mPowerProfile;
+ private final UsageBasedPowerEstimator mPowerEstimator;
public PhonePowerCalculator(PowerProfile powerProfile) {
- mPowerProfile = powerProfile;
+ mPowerEstimator = new UsageBasedPowerEstimator(
+ powerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE));
}
@Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
SparseArray<UserHandle> asUsers) {
- long phoneOnTimeMs = batteryStats.getPhoneOnTime(rawRealtimeUs,
+ final long phoneOnTimeMs = batteryStats.getPhoneOnTime(rawRealtimeUs,
BatteryStats.STATS_SINCE_CHARGED) / 1000;
- double phoneOnPower = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE)
- * phoneOnTimeMs / (60 * 60 * 1000);
+ final double phoneOnPower = mPowerEstimator.calculatePower(phoneOnTimeMs);
if (phoneOnPower != 0) {
builder.getOrCreateSystemBatteryConsumerBuilder(SystemBatteryConsumer.DRAIN_TYPE_PHONE)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE, phoneOnPower)
@@ -54,9 +54,8 @@
@Override
public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
- long phoneOnTimeMs = batteryStats.getPhoneOnTime(rawRealtimeUs, statsType) / 1000;
- double phoneOnPower = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE)
- * phoneOnTimeMs / (60 * 60 * 1000);
+ final long phoneOnTimeMs = batteryStats.getPhoneOnTime(rawRealtimeUs, statsType) / 1000;
+ final double phoneOnPower = mPowerEstimator.calculatePower(phoneOnTimeMs);
if (phoneOnPower != 0) {
BatterySipper bs = new BatterySipper(BatterySipper.DrainType.PHONE, null, 0);
bs.usagePowerMah = phoneOnPower;
diff --git a/core/java/com/android/internal/os/ScreenPowerCalculator.java b/core/java/com/android/internal/os/ScreenPowerCalculator.java
index 09fb75b..25f6b4d 100644
--- a/core/java/com/android/internal/os/ScreenPowerCalculator.java
+++ b/core/java/com/android/internal/os/ScreenPowerCalculator.java
@@ -16,7 +16,11 @@
package com.android.internal.os;
+import android.os.BatteryConsumer;
import android.os.BatteryStats;
+import android.os.BatteryUsageStats;
+import android.os.BatteryUsageStatsQuery;
+import android.os.SystemBatteryConsumer;
import android.os.UserHandle;
import android.util.Log;
import android.util.SparseArray;
@@ -30,10 +34,29 @@
private static final String TAG = "ScreenPowerCalculator";
private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
- private final PowerProfile mPowerProfile;
+ private final UsageBasedPowerEstimator mScreenOnPowerEstimator;
+ private final UsageBasedPowerEstimator mScreenFullPowerEstimator;
public ScreenPowerCalculator(PowerProfile powerProfile) {
- mPowerProfile = powerProfile;
+ mScreenOnPowerEstimator = new UsageBasedPowerEstimator(
+ powerProfile.getAveragePower(PowerProfile.POWER_SCREEN_ON));
+ mScreenFullPowerEstimator = new UsageBasedPowerEstimator(
+ powerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL));
+ }
+
+ @Override
+ public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
+ long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
+ SparseArray<UserHandle> asUsers) {
+ final long durationMs = computeDuration(batteryStats, rawRealtimeUs,
+ BatteryStats.STATS_SINCE_CHARGED);
+ final double powerMah = computePower(batteryStats, rawRealtimeUs,
+ BatteryStats.STATS_SINCE_CHARGED, durationMs);
+ if (powerMah != 0) {
+ builder.getOrCreateSystemBatteryConsumerBuilder(SystemBatteryConsumer.DRAIN_TYPE_SCREEN)
+ .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE, durationMs)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE, powerMah);
+ }
}
/**
@@ -42,30 +65,35 @@
@Override
public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
- double power = 0;
- final long screenOnTimeMs = batteryStats.getScreenOnTime(rawRealtimeUs, statsType) / 1000;
- power += screenOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_ON);
- final double screenFullPower =
- mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL);
- for (int i = 0; i < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; i++) {
- final double screenBinPower = screenFullPower * (i + 0.5f)
- / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
- final long brightnessTime =
- batteryStats.getScreenBrightnessTime(i, rawRealtimeUs, statsType) / 1000;
- final double p = screenBinPower * brightnessTime;
- if (DEBUG && p != 0) {
- Log.d(TAG, "Screen bin #" + i + ": time=" + brightnessTime
- + " power=" + formatCharge(p / (60 * 60 * 1000)));
- }
- power += p;
- }
- power /= (60 * 60 * 1000); // To hours
- if (power != 0) {
+ final long durationMs = computeDuration(batteryStats, rawRealtimeUs, statsType);
+ final double powerMah = computePower(batteryStats, rawRealtimeUs, statsType, durationMs);
+ if (powerMah != 0) {
final BatterySipper bs = new BatterySipper(BatterySipper.DrainType.SCREEN, null, 0);
- bs.usagePowerMah = power;
- bs.usageTimeMs = screenOnTimeMs;
+ bs.usagePowerMah = powerMah;
+ bs.usageTimeMs = durationMs;
bs.sumPower();
sippers.add(bs);
}
}
+
+ private long computeDuration(BatteryStats batteryStats, long rawRealtimeUs, int statsType) {
+ return batteryStats.getScreenOnTime(rawRealtimeUs, statsType) / 1000;
+ }
+
+ private double computePower(BatteryStats batteryStats, long rawRealtimeUs, int statsType,
+ long durationMs) {
+ double power = mScreenOnPowerEstimator.calculatePower(durationMs);
+ for (int i = 0; i < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; i++) {
+ final long brightnessTime =
+ batteryStats.getScreenBrightnessTime(i, rawRealtimeUs, statsType) / 1000;
+ final double binPowerMah = mScreenFullPowerEstimator.calculatePower(brightnessTime)
+ * (i + 0.5f) / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
+ if (DEBUG && binPowerMah != 0) {
+ Log.d(TAG, "Screen bin #" + i + ": time=" + brightnessTime
+ + " power=" + formatCharge(binPowerMah));
+ }
+ power += binPowerMah;
+ }
+ return power;
+ }
}
diff --git a/core/java/com/android/internal/os/SystemServicePowerCalculator.java b/core/java/com/android/internal/os/SystemServicePowerCalculator.java
index b15dff6..55fc1bb 100644
--- a/core/java/com/android/internal/os/SystemServicePowerCalculator.java
+++ b/core/java/com/android/internal/os/SystemServicePowerCalculator.java
@@ -16,7 +16,11 @@
package com.android.internal.os;
+import android.os.BatteryConsumer;
import android.os.BatteryStats;
+import android.os.BatteryUsageStats;
+import android.os.BatteryUsageStatsQuery;
+import android.os.UidBatteryConsumer;
import android.os.UserHandle;
import android.util.Log;
import android.util.SparseArray;
@@ -46,28 +50,35 @@
}
@Override
+ public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
+ long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
+ SparseArray<UserHandle> asUsers) {
+ calculateSystemServicePower(batteryStats);
+ super.calculate(builder, batteryStats, rawRealtimeUs, rawUptimeUs, query, asUsers);
+ }
+
+ @Override
+ protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
+ long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+ app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES,
+ calculateSystemServerCpuPowerMah(u));
+ }
+
+ @Override
public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, int statsType,
SparseArray<UserHandle> asUsers) {
- updateSystemServicePower(batteryStats);
+ calculateSystemServicePower(batteryStats);
super.calculate(sippers, batteryStats, rawRealtimeUs, rawUptimeUs, statsType, asUsers);
}
@Override
protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
long rawUptimeUs, int statsType) {
- final double proportionalUsage = u.getProportionalSystemServiceUsage();
- if (proportionalUsage > 0 && mSystemServicePowerMaUs != null) {
- double cpuPowerMaUs = 0;
- for (int i = 0; i < mSystemServicePowerMaUs.length; i++) {
- cpuPowerMaUs += mSystemServicePowerMaUs[i] * proportionalUsage;
- }
-
- app.systemServiceCpuPowerMah = cpuPowerMaUs / MICROSEC_IN_HR;
- }
+ app.systemServiceCpuPowerMah = calculateSystemServerCpuPowerMah(u);
}
- private void updateSystemServicePower(BatteryStats batteryStats) {
+ private void calculateSystemServicePower(BatteryStats batteryStats) {
final long[] systemServiceTimeAtCpuSpeeds = batteryStats.getSystemServiceTimeAtCpuSpeeds();
if (systemServiceTimeAtCpuSpeeds == null) {
return;
@@ -94,6 +105,17 @@
}
}
+ private double calculateSystemServerCpuPowerMah(BatteryStats.Uid u) {
+ double cpuPowerMaUs = 0;
+ final double proportionalUsage = u.getProportionalSystemServiceUsage();
+ if (proportionalUsage > 0 && mSystemServicePowerMaUs != null) {
+ for (int i = 0; i < mSystemServicePowerMaUs.length; i++) {
+ cpuPowerMaUs += mSystemServicePowerMaUs[i] * proportionalUsage;
+ }
+ }
+ return cpuPowerMaUs / MICROSEC_IN_HR;
+ }
+
@Override
public void reset() {
mSystemServicePowerMaUs = null;
diff --git a/core/java/com/android/internal/os/UsageBasedPowerEstimator.java b/core/java/com/android/internal/os/UsageBasedPowerEstimator.java
new file mode 100644
index 0000000..5910b61
--- /dev/null
+++ b/core/java/com/android/internal/os/UsageBasedPowerEstimator.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import android.os.BatteryStats;
+
+/**
+ * Implements a simple linear power model based on the assumption that the power consumer
+ * consumes a fixed current when it is used and no current when it is unused.
+ *
+ * <code>power = usageDuration * averagePower</code>
+ */
+public class UsageBasedPowerEstimator {
+ private static final double MILLIS_IN_HOUR = 1000.0 * 60 * 60;
+ private final double mAveragePowerMahPerMs;
+
+ public UsageBasedPowerEstimator(double averagePowerMilliAmp) {
+ mAveragePowerMahPerMs = averagePowerMilliAmp / MILLIS_IN_HOUR;
+ }
+
+ /**
+ * Given a {@link BatteryStats.Timer}, returns the accumulated duration.
+ */
+ public long calculateDuration(BatteryStats.Timer timer, long rawRealtimeUs, int statsType) {
+ return timer == null ? 0 : timer.getTotalTimeLocked(rawRealtimeUs, statsType) / 1000;
+ }
+
+ /**
+ * Given a duration in milliseconds, return the estimated power consumption.
+ */
+ public double calculatePower(long durationMs) {
+ return mAveragePowerMahPerMs * durationMs;
+ }
+}
diff --git a/core/java/com/android/internal/os/VideoPowerCalculator.java b/core/java/com/android/internal/os/VideoPowerCalculator.java
new file mode 100644
index 0000000..5d6caf5
--- /dev/null
+++ b/core/java/com/android/internal/os/VideoPowerCalculator.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.os;
+
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.BatteryUsageStatsQuery;
+import android.os.UidBatteryConsumer;
+
+/**
+ * A {@link PowerCalculator} to calculate power consumed by video hardware.
+ *
+ * Also see {@link PowerProfile#POWER_VIDEO}.
+ */
+public class VideoPowerCalculator extends PowerCalculator {
+ private final UsageBasedPowerEstimator mPowerEstimator;
+
+ public VideoPowerCalculator(PowerProfile powerProfile) {
+ mPowerEstimator = new UsageBasedPowerEstimator(
+ powerProfile.getAveragePower(PowerProfile.POWER_VIDEO));
+ }
+
+ @Override
+ protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
+ long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+ final long durationMs = mPowerEstimator.calculateDuration(u.getVideoTurnedOnTimer(),
+ rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED);
+ final double powerMah = mPowerEstimator.calculatePower(durationMs);
+ app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_VIDEO, durationMs)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_VIDEO, powerMah);
+ }
+}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 8c9da66..94ef64f 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -347,12 +347,6 @@
},
},
- product_variables: {
- experimental_mte: {
- cflags: ["-DANDROID_EXPERIMENTAL_MTE"],
- },
- },
-
// Workaround Clang LTO crash.
lto: {
never: true,
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index efede21..2ff474b 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -14,15 +14,6 @@
* limitations under the License.
*/
-/*
- * Disable optimization of this file if we are compiling with the address
- * sanitizer. This is a mitigation for b/122921367 and can be removed once the
- * bug is fixed.
- */
-#if __has_feature(address_sanitizer)
-#pragma clang optimize off
-#endif
-
#define LOG_TAG "Zygote"
#define ATRACE_TAG ATRACE_TAG_DALVIK
@@ -843,7 +834,7 @@
PrepareDir(user_source, 0710, user_id ? AID_ROOT : AID_SHELL,
multiuser_get_uid(user_id, AID_EVERYBODY), fail_fn);
- bool isAppDataIsolationEnabled = GetBoolProperty(kVoldAppDataIsolation, true);
+ bool isAppDataIsolationEnabled = GetBoolProperty(kVoldAppDataIsolation, false);
if (mount_mode == MOUNT_EXTERNAL_PASS_THROUGH) {
const std::string pass_through_source = StringPrintf("/mnt/pass_through/%d", user_id);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 3053518..ce3ed9d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2676,11 +2676,11 @@
The app can check whether it has this authorization by calling
{@link android.provider.Settings#canDrawOverlays
Settings.canDrawOverlays()}.
- <p>Protection level: signature|preinstalled|appop|pre23|development -->
+ <p>Protection level: signature|appop|installer|pre23|development -->
<permission android:name="android.permission.SYSTEM_ALERT_WINDOW"
android:label="@string/permlab_systemAlertWindow"
android:description="@string/permdesc_systemAlertWindow"
- android:protectionLevel="signature|preinstalled|appop|pre23|development" />
+ android:protectionLevel="signature|appop|installer|pre23|development" />
<!-- @deprecated Use {@link android.Manifest.permission#REQUEST_COMPANION_RUN_IN_BACKGROUND}
@hide
@@ -3591,6 +3591,14 @@
<permission android:name="android.permission.BIND_CONTENT_CAPTURE_SERVICE"
android:protectionLevel="signature" />
+ <!-- Must be required by a android.service.translation.TranslationService,
+ to ensure that only the system can bind to it.
+ @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_TRANSLATION_SERVICE"
+ android:protectionLevel="signature" />
+
<!-- Must be required by a android.service.contentsuggestions.ContentSuggestionsService,
to ensure that only the system can bind to it.
@SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 7ca3faf..ef54db1a 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -8292,6 +8292,23 @@
</declare-styleable>
<!-- =============================== -->
+ <!-- Translation attributes -->
+ <!-- =============================== -->
+ <eat-comment />
+
+ <!-- Use <code>translation-service</code> as the root tag of the XML resource that describes
+ a {@link android.service.translation.TranslationService}, which is referenced from
+ its {@link android.service.translation.TranslationService#SERVICE_META_DATA} meta-data
+ entry.
+ @hide @SystemApi
+ -->
+ <declare-styleable name="TranslationService">
+ <!-- Fully qualified class name of an activity that allows the user to modify
+ the settings for this service. -->
+ <attr name="settingsActivity" />
+ </declare-styleable>
+
+ <!-- =============================== -->
<!-- Contacts meta-data attributes -->
<!-- =============================== -->
<eat-comment />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index da658cc..487847a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -450,6 +450,10 @@
-->
</string-array>
+ <!-- Whether the internal vehicle network should remain active even when no
+ apps requested it. -->
+ <bool name="config_vehicleInternalNetworkAlwaysRequested">false</bool>
+
<!-- Configuration of network interfaces that support WakeOnLAN -->
<string-array translatable="false" name="config_wakeonlan_supported_interfaces">
<!--
@@ -3724,6 +3728,14 @@
-->
<string name="config_defaultAugmentedAutofillService" translatable="false"></string>
+ <!-- The package name for the system's translation service.
+ This service must be trusted, as it can be activated without explicit consent of the user.
+ If no service with the specified name exists on the device, translation wil be
+ disabled.
+ Example: "com.android.translation/.TranslationService"
+-->
+ <string name="config_defaultTranslationService" translatable="false"></string>
+
<!-- The package name for the system's app prediction service.
This service must be trusted, as it can be activated without explicit consent of the user.
Example: "com.android.intelligence/.AppPredictionService"
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 31b4edd..0447249 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -684,6 +684,7 @@
<java-symbol type="string" name="config_ethernet_iface_regex" />
<java-symbol type="string" name="not_checked" />
<java-symbol type="array" name="config_ethernet_interfaces" />
+ <java-symbol type="bool" name="config_vehicleInternalNetworkAlwaysRequested" />
<java-symbol type="array" name="config_wakeonlan_supported_interfaces" />
<java-symbol type="string" name="config_forceVoiceInteractionServicePackage" />
<java-symbol type="string" name="config_mms_user_agent" />
@@ -3484,6 +3485,7 @@
<java-symbol type="string" name="config_defaultWellbeingPackage" />
<java-symbol type="string" name="config_defaultContentCaptureService" />
<java-symbol type="string" name="config_defaultAugmentedAutofillService" />
+ <java-symbol type="string" name="config_defaultTranslationService" />
<java-symbol type="string" name="config_defaultAppPredictionService" />
<java-symbol type="string" name="config_defaultContentSuggestionsService" />
<java-symbol type="string" name="config_defaultSearchUiService" />
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/SetSchemaRequestTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/SetSchemaRequestTest.java
index 133efce..aab9229 100644
--- a/core/tests/coretests/src/android/app/appsearch/external/app/SetSchemaRequestTest.java
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/SetSchemaRequestTest.java
@@ -16,6 +16,7 @@
package android.app.appsearch;
+
import static com.google.common.truth.Truth.assertThat;
import static org.testng.Assert.expectThrows;
diff --git a/core/tests/coretests/src/android/graphics/OWNERS b/core/tests/coretests/src/android/graphics/OWNERS
new file mode 100644
index 0000000..1e8478e
--- /dev/null
+++ b/core/tests/coretests/src/android/graphics/OWNERS
@@ -0,0 +1,6 @@
+# Bug component: 24939
+
+include /graphics/java/android/graphics/OWNERS
+
+per-file Font* = file:/graphics/java/android/graphics/fonts/OWNERS
+per-file Typeface* = file:/graphics/java/android/graphics/fonts/OWNERS
diff --git a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
index 82d066f..05ff218 100644
--- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
+++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
@@ -67,6 +67,7 @@
private static final String TEST_FONT_DIR;
private static final String TEST_OEM_XML;
private static final String TEST_OEM_DIR;
+ private static final String TEST_UPDATABLE_FONT_DIR;
private static final float GLYPH_1EM_WIDTH;
private static final float GLYPH_2EM_WIDTH;
@@ -82,9 +83,11 @@
TEST_FONTS_XML = new File(cacheDir, "fonts.xml").getAbsolutePath();
TEST_OEM_DIR = cacheDir.getAbsolutePath() + "/oem_fonts/";
TEST_OEM_XML = new File(cacheDir, "fonts_customization.xml").getAbsolutePath();
+ TEST_UPDATABLE_FONT_DIR = cacheDir.getAbsolutePath() + "/updatable_fonts/";
new File(TEST_FONT_DIR).mkdirs();
new File(TEST_OEM_DIR).mkdirs();
+ new File(TEST_UPDATABLE_FONT_DIR).mkdirs();
final AssetManager am =
InstrumentationRegistry.getInstrumentation().getContext().getAssets();
@@ -103,18 +106,11 @@
InstrumentationRegistry.getInstrumentation().getContext().getAssets();
for (final String fontFile : TEST_FONT_FILES) {
final String sourceInAsset = "fonts/" + fontFile;
- final File outInCache = new File(TEST_FONT_DIR, fontFile);
- try (InputStream is = am.open(sourceInAsset)) {
- Files.copy(is, outInCache.toPath(), StandardCopyOption.REPLACE_EXISTING);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- final File outOemInCache = new File(TEST_OEM_DIR, fontFile);
- try (InputStream is = am.open(sourceInAsset)) {
- Files.copy(is, outOemInCache.toPath(), StandardCopyOption.REPLACE_EXISTING);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
+ copyAssetToFile(sourceInAsset, new File(TEST_FONT_DIR, fontFile));
+ copyAssetToFile(sourceInAsset, new File(TEST_OEM_DIR, fontFile));
+ }
+ for (final File fontFile : new File(TEST_UPDATABLE_FONT_DIR).listFiles()) {
+ fontFile.delete();
}
}
@@ -124,7 +120,20 @@
final File outInCache = new File(TEST_FONT_DIR, fontFile);
outInCache.delete();
final File outOemInCache = new File(TEST_OEM_DIR, fontFile);
- outInCache.delete();
+ outOemInCache.delete();
+ }
+ for (final File fontFile : new File(TEST_UPDATABLE_FONT_DIR).listFiles()) {
+ fontFile.delete();
+ }
+ }
+
+ private static void copyAssetToFile(String sourceInAsset, File out) {
+ final AssetManager am =
+ InstrumentationRegistry.getInstrumentation().getContext().getAssets();
+ try (InputStream is = am.open(sourceInAsset)) {
+ Files.copy(is, out.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
}
}
@@ -138,7 +147,7 @@
}
final FontConfig.Alias[] aliases = SystemFonts.buildSystemFallback(TEST_FONTS_XML,
- TEST_FONT_DIR, oemCustomization, fallbackMap);
+ TEST_FONT_DIR, TEST_UPDATABLE_FONT_DIR, oemCustomization, fallbackMap);
Typeface.initSystemDefaultTypefaces(fontMap, fallbackMap, aliases);
}
@@ -835,4 +844,32 @@
+ "</fonts-modification>";
readFontCustomization(oemXml);
}
+
+
+ @Test
+ public void testBuildSystemFallback_UpdatableFont() {
+ final String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ + "<familyset>"
+ + " <family name='test'>"
+ + " <font weight='400' style='normal'>a3em.ttf</font>"
+ + " </family>"
+ + "</familyset>";
+ final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
+ final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
+ final FontCustomizationParser.Result oemCustomization =
+ new FontCustomizationParser.Result();
+
+ // Install all2em.ttf as a3em.ttf
+ copyAssetToFile("fonts/all2em.ttf", new File(TEST_UPDATABLE_FONT_DIR, "a3em.ttf"));
+ buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap);
+
+ final Paint paint = new Paint();
+
+ final Typeface sansSerifTypeface = fontMap.get("test");
+ assertNotNull(sansSerifTypeface);
+ paint.setTypeface(sansSerifTypeface);
+ assertEquals(GLYPH_2EM_WIDTH, paint.measureText("a"), 0.0f);
+ assertEquals(GLYPH_2EM_WIDTH, paint.measureText("b"), 0.0f);
+ assertEquals(GLYPH_2EM_WIDTH, paint.measureText("c"), 0.0f);
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java
new file mode 100644
index 0000000..e2a1064
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.BatteryConsumer;
+import android.os.SystemBatteryConsumer;
+import android.view.Display;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class AmbientDisplayPowerCalculatorTest {
+ private static final double PRECISION = 0.00001;
+
+ @Rule
+ public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+ .setAveragePower(PowerProfile.POWER_AMBIENT_DISPLAY, 360.0);
+
+ @Test
+ public void testTimerBasedModel() {
+ BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+
+ stats.noteScreenStateLocked(Display.STATE_ON, 1000, 1000, 1000);
+ stats.noteScreenStateLocked(Display.STATE_DOZE, 2000, 2000, 2000);
+ stats.noteScreenStateLocked(Display.STATE_OFF, 3000, 3000, 3000);
+
+ AmbientDisplayPowerCalculator calculator =
+ new AmbientDisplayPowerCalculator(mStatsRule.getPowerProfile());
+
+ mStatsRule.apply(calculator);
+
+ SystemBatteryConsumer consumer =
+ mStatsRule.getSystemBatteryConsumer(
+ SystemBatteryConsumer.DRAIN_TYPE_AMBIENT_DISPLAY);
+ assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE))
+ .isEqualTo(1000);
+ assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE))
+ .isWithin(PRECISION).of(0.1);
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/AudioPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/AudioPowerCalculatorTest.java
new file mode 100644
index 0000000..ed4638c
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/AudioPowerCalculatorTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.BatteryConsumer;
+import android.os.Process;
+import android.os.UidBatteryConsumer;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class AudioPowerCalculatorTest {
+ private static final double PRECISION = 0.00001;
+
+ private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
+
+ @Rule
+ public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+ .setAveragePower(PowerProfile.POWER_AUDIO, 360.0);
+
+ @Test
+ public void testTimerBasedModel() {
+ BatteryStatsImpl.Uid uidStats = mStatsRule.getUidStats(APP_UID);
+ uidStats.noteAudioTurnedOnLocked(1000);
+ uidStats.noteAudioTurnedOffLocked(2000);
+
+ AudioPowerCalculator calculator =
+ new AudioPowerCalculator(mStatsRule.getPowerProfile());
+
+ mStatsRule.apply(calculator);
+
+ UidBatteryConsumer consumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+ assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_AUDIO))
+ .isEqualTo(1000);
+ assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AUDIO))
+ .isWithin(PRECISION).of(0.1);
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index bd41542..8ff318e 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -21,6 +21,8 @@
@RunWith(Suite.class)
@Suite.SuiteClasses({
+ AmbientDisplayPowerCalculatorTest.class,
+ AudioPowerCalculatorTest.class,
BatteryStatsCpuTimesTest.class,
BatteryStatsBackgroundStatsTest.class,
BatteryStatsBinderCallStatsTest.class,
@@ -42,6 +44,9 @@
BatteryStatsUserLifecycleTests.class,
BluetoothPowerCalculatorTest.class,
BstatsCpuTimesValidationTest.class,
+ CameraPowerCalculatorTest.class,
+ FlashlightPowerCalculatorTest.class,
+ IdlePowerCalculatorTest.class,
KernelCpuProcStringReaderTest.class,
KernelCpuUidActiveTimeReaderTest.class,
KernelCpuUidBpfMapReaderTest.class,
@@ -55,6 +60,9 @@
LongSamplingCounterArrayTest.class,
PowerCalculatorTest.class,
PowerProfileTest.class,
+ ScreenPowerCalculatorTest.class,
+ SystemServicePowerCalculatorTest.class,
+ VideoPowerCalculatorTest.class,
com.android.internal.power.MeasuredEnergyStatsTest.class
})
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
new file mode 100644
index 0000000..55f64f9
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
@@ -0,0 +1,130 @@
+/*
+ * 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.os;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.BatteryStats;
+import android.os.BatteryUsageStats;
+import android.os.BatteryUsageStatsQuery;
+import android.os.SystemBatteryConsumer;
+import android.os.UidBatteryConsumer;
+import android.util.SparseArray;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+public class BatteryUsageStatsRule implements TestRule {
+ private final PowerProfile mPowerProfile;
+ private final MockClocks mMockClocks = new MockClocks();
+ private final MockBatteryStatsImpl mBatteryStats = new MockBatteryStatsImpl(mMockClocks) {
+ @Override
+ public boolean hasBluetoothActivityReporting() {
+ return true;
+ }
+ };
+
+ private BatteryUsageStats mBatteryUsageStats;
+
+ public BatteryUsageStatsRule() {
+ Context context = InstrumentationRegistry.getContext();
+ mPowerProfile = spy(new PowerProfile(context, true /* forTest */));
+ mBatteryStats.setPowerProfile(mPowerProfile);
+ }
+
+ public BatteryUsageStatsRule setAveragePower(String key, double value) {
+ when(mPowerProfile.getAveragePower(key)).thenReturn(value);
+ return this;
+ }
+
+ public BatteryUsageStatsRule setAveragePower(String key, double[] values) {
+ when(mPowerProfile.getNumElements(key)).thenReturn(values.length);
+ for (int i = 0; i < values.length; i++) {
+ when(mPowerProfile.getAveragePower(key, i)).thenReturn(values[i]);
+ }
+ return this;
+ }
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ noteOnBattery();
+ base.evaluate();
+ }
+ };
+ }
+
+ private void noteOnBattery() {
+ mBatteryStats.getOnBatteryTimeBase().setRunning(true, 0, 0);
+ }
+
+ public PowerProfile getPowerProfile() {
+ return mPowerProfile;
+ }
+
+ public MockBatteryStatsImpl getBatteryStats() {
+ return mBatteryStats;
+ }
+
+ public BatteryStatsImpl.Uid getUidStats(int uid) {
+ return mBatteryStats.getUidStatsLocked(uid);
+ }
+
+ public void setTime(long realtimeUs, long uptimeUs) {
+ mMockClocks.realtime = realtimeUs;
+ mMockClocks.uptime = uptimeUs;
+ }
+
+ void apply(PowerCalculator calculator) {
+ BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(0, 0, false);
+ SparseArray<? extends BatteryStats.Uid> uidStats = mBatteryStats.getUidStats();
+ for (int i = 0; i < uidStats.size(); i++) {
+ builder.getOrCreateUidBatteryConsumerBuilder(uidStats.valueAt(i));
+ }
+
+ calculator.calculate(builder, mBatteryStats, mMockClocks.realtime, mMockClocks.uptime,
+ BatteryUsageStatsQuery.DEFAULT, null);
+
+ mBatteryUsageStats = builder.build();
+ }
+
+ public UidBatteryConsumer getUidBatteryConsumer(int uid) {
+ for (UidBatteryConsumer ubc : mBatteryUsageStats.getUidBatteryConsumers()) {
+ if (ubc.getUid() == uid) {
+ return ubc;
+ }
+ }
+ return null;
+ }
+
+ public SystemBatteryConsumer getSystemBatteryConsumer(
+ @SystemBatteryConsumer.DrainType int drainType) {
+ for (SystemBatteryConsumer sbc : mBatteryUsageStats.getSystemBatteryConsumers()) {
+ if (sbc.getDrainType() == drainType) {
+ return sbc;
+ }
+ }
+ return null;
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java
index 96eb8da..e559471 100644
--- a/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java
@@ -18,24 +18,17 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.when;
-
import android.annotation.Nullable;
import android.os.BatteryConsumer;
-import android.os.BatteryUsageStats;
-import android.os.BatteryUsageStatsQuery;
import android.os.Process;
import android.os.SystemBatteryConsumer;
-import android.os.UidBatteryConsumer;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -43,83 +36,69 @@
private static final double PRECISION = 0.00001;
private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
- @Mock
- private PowerProfile mMockPowerProfile;
- private MockBatteryStatsImpl mMockBatteryStats;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mMockBatteryStats = new MockBatteryStatsImpl(new MockClocks()) {
- @Override
- public boolean hasBluetoothActivityReporting() {
- return true;
- }
- };
- mMockBatteryStats.getOnBatteryTimeBase().setRunning(true, 100_000, 100_000);
- when(mMockPowerProfile.getAveragePower(
- PowerProfile.POWER_BLUETOOTH_CONTROLLER_IDLE)).thenReturn(10.0);
- when(mMockPowerProfile.getAveragePower(
- PowerProfile.POWER_BLUETOOTH_CONTROLLER_RX)).thenReturn(50.0);
- when(mMockPowerProfile.getAveragePower(
- PowerProfile.POWER_BLUETOOTH_CONTROLLER_TX)).thenReturn(100.0);
- }
+ @Rule
+ public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+ .setAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_IDLE, 10.0)
+ .setAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_RX, 50.0)
+ .setAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_TX, 100.0);
@Test
public void testTimerBasedModel() {
- setDurationsAndPower(
- mMockBatteryStats.getUidStatsLocked(Process.BLUETOOTH_UID)
+ setDurationsAndPower(mStatsRule.getUidStats(Process.BLUETOOTH_UID)
.getOrCreateBluetoothControllerActivityLocked(),
1000, 2000, 3000, 0);
- setDurationsAndPower(mMockBatteryStats.getUidStatsLocked(APP_UID)
+ setDurationsAndPower(mStatsRule.getUidStats(APP_UID)
.getOrCreateBluetoothControllerActivityLocked(),
4000, 5000, 6000, 0);
setDurationsAndPower((BatteryStatsImpl.ControllerActivityCounterImpl)
- mMockBatteryStats.getBluetoothControllerActivity(),
+ mStatsRule.getBatteryStats().getBluetoothControllerActivity(),
6000, 8000, 10000, 0);
- BatteryUsageStats batteryUsageStats = buildBatteryUsageStats();
+ BluetoothPowerCalculator calculator =
+ new BluetoothPowerCalculator(mStatsRule.getPowerProfile());
+
+ mStatsRule.apply(calculator);
assertBluetoothPowerAndDuration(
- getUidBatteryConsumer(batteryUsageStats, Process.BLUETOOTH_UID),
+ mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID),
0.11388, 6000);
assertBluetoothPowerAndDuration(
- getUidBatteryConsumer(batteryUsageStats, APP_UID),
+ mStatsRule.getUidBatteryConsumer(APP_UID),
0.24722, 15000);
assertBluetoothPowerAndDuration(
- getBluetoothSystemBatteryConsumer(batteryUsageStats,
- SystemBatteryConsumer.DRAIN_TYPE_BLUETOOTH),
+ mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_BLUETOOTH),
0.15833, 9000);
}
@Test
public void testReportedPowerBasedModel() {
- setDurationsAndPower(
- mMockBatteryStats.getUidStatsLocked(Process.BLUETOOTH_UID)
+ setDurationsAndPower(mStatsRule.getUidStats(Process.BLUETOOTH_UID)
.getOrCreateBluetoothControllerActivityLocked(),
1000, 2000, 3000, 360000);
- setDurationsAndPower(mMockBatteryStats.getUidStatsLocked(APP_UID)
+ setDurationsAndPower(mStatsRule.getUidStats(APP_UID)
.getOrCreateBluetoothControllerActivityLocked(),
4000, 5000, 6000, 720000);
setDurationsAndPower((BatteryStatsImpl.ControllerActivityCounterImpl)
- mMockBatteryStats.getBluetoothControllerActivity(),
+ mStatsRule.getBatteryStats().getBluetoothControllerActivity(),
6000, 8000, 10000, 1260000);
- BatteryUsageStats batteryUsageStats = buildBatteryUsageStats();
+ BluetoothPowerCalculator calculator =
+ new BluetoothPowerCalculator(mStatsRule.getPowerProfile());
+
+ mStatsRule.apply(calculator);
assertBluetoothPowerAndDuration(
- getUidBatteryConsumer(batteryUsageStats, Process.BLUETOOTH_UID),
+ mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID),
0.1, 6000);
assertBluetoothPowerAndDuration(
- getUidBatteryConsumer(batteryUsageStats, APP_UID),
+ mStatsRule.getUidBatteryConsumer(APP_UID),
0.2, 15000);
assertBluetoothPowerAndDuration(
- getBluetoothSystemBatteryConsumer(batteryUsageStats,
- SystemBatteryConsumer.DRAIN_TYPE_BLUETOOTH),
+ mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_BLUETOOTH),
0.15, 9000);
}
@@ -132,38 +111,6 @@
controllerActivity.getPowerCounter().addCountLocked(powerMaMs);
}
- private BatteryUsageStats buildBatteryUsageStats() {
- BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(0, 0, false);
- builder.getOrCreateUidBatteryConsumerBuilder(
- mMockBatteryStats.getUidStatsLocked(Process.BLUETOOTH_UID));
- builder.getOrCreateUidBatteryConsumerBuilder(
- mMockBatteryStats.getUidStatsLocked(APP_UID));
-
- BluetoothPowerCalculator bpc = new BluetoothPowerCalculator(mMockPowerProfile);
- bpc.calculate(builder, mMockBatteryStats, 200_000, 200_000, BatteryUsageStatsQuery.DEFAULT,
- null);
- return builder.build();
- }
-
- private UidBatteryConsumer getUidBatteryConsumer(BatteryUsageStats batteryUsageStats, int uid) {
- for (UidBatteryConsumer ubc : batteryUsageStats.getUidBatteryConsumers()) {
- if (ubc.getUid() == uid) {
- return ubc;
- }
- }
- return null;
- }
-
- private SystemBatteryConsumer getBluetoothSystemBatteryConsumer(
- BatteryUsageStats batteryUsageStats, int drainType) {
- for (SystemBatteryConsumer sbc : batteryUsageStats.getSystemBatteryConsumers()) {
- if (sbc.getDrainType() == drainType) {
- return sbc;
- }
- }
- return null;
- }
-
private void assertBluetoothPowerAndDuration(@Nullable BatteryConsumer batteryConsumer,
double powerMah, int durationMs) {
assertThat(batteryConsumer).isNotNull();
diff --git a/core/tests/coretests/src/com/android/internal/os/CameraPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/CameraPowerCalculatorTest.java
new file mode 100644
index 0000000..a21dd58
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/CameraPowerCalculatorTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.BatteryConsumer;
+import android.os.Process;
+import android.os.UidBatteryConsumer;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CameraPowerCalculatorTest {
+ private static final double PRECISION = 0.00001;
+
+ private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
+
+ @Rule
+ public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+ .setAveragePower(PowerProfile.POWER_CAMERA, 360.0);
+
+ @Test
+ public void testTimerBasedModel() {
+ BatteryStatsImpl.Uid uidStats = mStatsRule.getUidStats(APP_UID);
+ uidStats.noteCameraTurnedOnLocked(1000);
+ uidStats.noteCameraTurnedOffLocked(2000);
+
+ CameraPowerCalculator calculator =
+ new CameraPowerCalculator(mStatsRule.getPowerProfile());
+
+ mStatsRule.apply(calculator);
+
+ UidBatteryConsumer consumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+ assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CAMERA))
+ .isEqualTo(1000);
+ assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
+ .isWithin(PRECISION).of(0.1);
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/FlashlightPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/FlashlightPowerCalculatorTest.java
new file mode 100644
index 0000000..b7bbedd
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/FlashlightPowerCalculatorTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.BatteryConsumer;
+import android.os.Process;
+import android.os.UidBatteryConsumer;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class FlashlightPowerCalculatorTest {
+ private static final double PRECISION = 0.00001;
+
+ private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
+
+ @Rule
+ public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+ .setAveragePower(PowerProfile.POWER_FLASHLIGHT, 360.0);
+
+ @Test
+ public void testTimerBasedModel() {
+ BatteryStatsImpl.Uid uidStats = mStatsRule.getUidStats(APP_UID);
+ uidStats.noteFlashlightTurnedOnLocked(1000);
+ uidStats.noteFlashlightTurnedOffLocked(2000);
+
+ FlashlightPowerCalculator calculator =
+ new FlashlightPowerCalculator(mStatsRule.getPowerProfile());
+
+ mStatsRule.apply(calculator);
+
+ UidBatteryConsumer consumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+ assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_FLASHLIGHT))
+ .isEqualTo(1000);
+ assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
+ .isWithin(PRECISION).of(0.1);
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/IdlePowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/IdlePowerCalculatorTest.java
new file mode 100644
index 0000000..781e725
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/IdlePowerCalculatorTest.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.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.BatteryConsumer;
+import android.os.SystemBatteryConsumer;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IdlePowerCalculatorTest {
+ private static final double PRECISION = 0.00001;
+
+ @Rule
+ public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+ .setAveragePower(PowerProfile.POWER_CPU_IDLE, 720.0)
+ .setAveragePower(PowerProfile.POWER_CPU_SUSPEND, 360.0);
+
+ @Test
+ public void testTimerBasedModel() {
+ mStatsRule.setTime(3_000_000, 2_000_000);
+
+ IdlePowerCalculator calculator = new IdlePowerCalculator(mStatsRule.getPowerProfile());
+
+ mStatsRule.apply(calculator);
+
+ SystemBatteryConsumer consumer =
+ mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_IDLE);
+ assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE))
+ .isEqualTo(3000);
+ assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE))
+ .isWithin(PRECISION).of(0.7);
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/MemoryPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/MemoryPowerCalculatorTest.java
new file mode 100644
index 0000000..8f21503
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/MemoryPowerCalculatorTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.BatteryConsumer;
+import android.os.SystemBatteryConsumer;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MemoryPowerCalculatorTest {
+ private static final double PRECISION = 0.00001;
+
+ @Rule
+ public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+ .setAveragePower(PowerProfile.POWER_MEMORY, new double[] {360.0, 720.0, 1080.0});
+
+ @Test
+ public void testTimerBasedModel() {
+ BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+
+ // First update establishes a baseline
+ stats.getKernelMemoryTimerLocked(0).update(0, 1, 0);
+ stats.getKernelMemoryTimerLocked(2).update(0, 1, 0);
+
+ stats.getKernelMemoryTimerLocked(0).update(1000000, 1, 4000000);
+ stats.getKernelMemoryTimerLocked(2).update(2000000, 1, 8000000);
+
+ MemoryPowerCalculator calculator =
+ new MemoryPowerCalculator(mStatsRule.getPowerProfile());
+
+ mStatsRule.apply(calculator);
+
+ SystemBatteryConsumer consumer =
+ mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_MEMORY);
+ assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE))
+ .isEqualTo(3000);
+ assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE))
+ .isWithin(PRECISION).of(0.7);
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index c775137..fc23721 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -46,6 +46,10 @@
mOnBatteryTimeBase);
mScreenDozeTimer = new BatteryStatsImpl.StopwatchTimer(clocks, null, -1, null,
mOnBatteryTimeBase);
+ for (int i = 0; i < mScreenBrightnessTimer.length; i++) {
+ mScreenBrightnessTimer[i] = new BatteryStatsImpl.StopwatchTimer(clocks, null, -1, null,
+ mOnBatteryTimeBase);
+ }
mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase);
mBluetoothActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase, 1);
setExternalStatsSyncLocked(new DummyExternalStatsSync());
diff --git a/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
new file mode 100644
index 0000000..e43caa3
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.BatteryConsumer;
+import android.os.SystemBatteryConsumer;
+import android.view.Display;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ScreenPowerCalculatorTest {
+ private static final double PRECISION = 0.00001;
+
+ @Rule
+ public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+ .setAveragePower(PowerProfile.POWER_SCREEN_ON, 360.0)
+ .setAveragePower(PowerProfile.POWER_SCREEN_FULL, 3600.0);
+
+ @Test
+ public void testTimerBasedModel() {
+ BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+
+ stats.noteScreenStateLocked(Display.STATE_ON, 1000, 1000, 1000);
+ stats.noteScreenBrightnessLocked(100, 1000, 1000);
+ stats.noteScreenBrightnessLocked(200, 2000, 2000);
+ stats.noteScreenStateLocked(Display.STATE_OFF, 3000, 3000, 3000);
+
+ ScreenPowerCalculator calculator =
+ new ScreenPowerCalculator(mStatsRule.getPowerProfile());
+
+ mStatsRule.apply(calculator);
+
+ SystemBatteryConsumer consumer =
+ mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_SCREEN);
+ assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE))
+ .isEqualTo(2000);
+ assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE))
+ .isWithin(PRECISION).of(1.2);
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
index dbb36fb..a5cafb9 100644
--- a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
@@ -16,50 +16,46 @@
package com.android.internal.os;
-import static org.junit.Assert.assertEquals;
+import static com.google.common.truth.Truth.assertThat;
-import android.content.Context;
-import android.os.BatteryStats;
+import android.os.BatteryConsumer;
import android.os.Binder;
import android.os.Process;
import androidx.annotation.Nullable;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.List;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class SystemServicePowerCalculatorTest {
- private PowerProfile mProfile;
+ private static final double PRECISION = 0.0000001;
+
+ @Rule
+ public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule();
+
private MockBatteryStatsImpl mMockBatteryStats;
private MockKernelCpuUidFreqTimeReader mMockCpuUidFreqTimeReader;
private MockSystemServerCpuThreadReader mMockSystemServerCpuThreadReader;
- private SystemServicePowerCalculator mSystemServicePowerCalculator;
@Before
public void setUp() throws IOException {
- Context context = InstrumentationRegistry.getContext();
- mProfile = new PowerProfile(context, true /* forTest */);
mMockSystemServerCpuThreadReader = new MockSystemServerCpuThreadReader();
mMockCpuUidFreqTimeReader = new MockKernelCpuUidFreqTimeReader();
- mMockBatteryStats = new MockBatteryStatsImpl(new MockClocks())
- .setPowerProfile(mProfile)
+ mMockBatteryStats = mStatsRule.getBatteryStats()
.setSystemServerCpuThreadReader(mMockSystemServerCpuThreadReader)
.setKernelCpuUidFreqTimeReader(mMockCpuUidFreqTimeReader)
.setUserInfoProvider(new MockUserInfoProvider());
- mMockBatteryStats.getOnBatteryTimeBase().setRunning(true, 0, 0);
- mSystemServicePowerCalculator = new SystemServicePowerCalculator(mProfile);
}
@Test
@@ -103,15 +99,17 @@
mMockBatteryStats.updateSystemServiceCallStats();
mMockBatteryStats.updateSystemServerThreadStats();
- BatterySipper app1 = new BatterySipper(BatterySipper.DrainType.APP,
- mMockBatteryStats.getUidStatsLocked(workSourceUid1), 0);
- BatterySipper app2 = new BatterySipper(BatterySipper.DrainType.APP,
- mMockBatteryStats.getUidStatsLocked(workSourceUid2), 0);
- mSystemServicePowerCalculator.calculate(List.of(app1, app2), mMockBatteryStats, 0, 0,
- BatteryStats.STATS_SINCE_CHARGED, null);
+ SystemServicePowerCalculator calculator = new SystemServicePowerCalculator(
+ mStatsRule.getPowerProfile());
- assertEquals(0.00016269, app1.systemServiceCpuPowerMah, 0.0000001);
- assertEquals(0.00146426, app2.systemServiceCpuPowerMah, 0.0000001);
+ mStatsRule.apply(calculator);
+
+ assertThat(mStatsRule.getUidBatteryConsumer(workSourceUid1)
+ .getConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES))
+ .isWithin(PRECISION).of(0.00016269);
+ assertThat(mStatsRule.getUidBatteryConsumer(workSourceUid2)
+ .getConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES))
+ .isWithin(PRECISION).of(0.00146426);
}
private static class MockKernelCpuUidFreqTimeReader extends
diff --git a/core/tests/coretests/src/com/android/internal/os/VideoPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/VideoPowerCalculatorTest.java
new file mode 100644
index 0000000..39eac49
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/VideoPowerCalculatorTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.BatteryConsumer;
+import android.os.Process;
+import android.os.UidBatteryConsumer;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VideoPowerCalculatorTest {
+ private static final double PRECISION = 0.00001;
+
+ private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
+
+ @Rule
+ public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+ .setAveragePower(PowerProfile.POWER_VIDEO, 360.0);
+
+ @Test
+ public void testTimerBasedModel() {
+ BatteryStatsImpl.Uid uidStats = mStatsRule.getUidStats(APP_UID);
+ uidStats.noteVideoTurnedOnLocked(1000);
+ uidStats.noteVideoTurnedOffLocked(2000);
+
+ VideoPowerCalculator calculator =
+ new VideoPowerCalculator(mStatsRule.getPowerProfile());
+
+ mStatsRule.apply(calculator);
+
+ UidBatteryConsumer consumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+ assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_VIDEO))
+ .isEqualTo(1000);
+ assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_VIDEO))
+ .isWithin(PRECISION).of(0.1);
+ }
+}
diff --git a/data/etc/OWNERS b/data/etc/OWNERS
index 5efd0bd..9867d81 100644
--- a/data/etc/OWNERS
+++ b/data/etc/OWNERS
@@ -11,3 +11,5 @@
toddke@android.com
toddke@google.com
yamasani@google.com
+
+per-file preinstalled-packages* = file:/MULTIUSER_OWNERS
\ No newline at end of file
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 0782f8d..73fff72 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -16,6 +16,7 @@
package android.graphics;
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.fonts.FontVariationAxis;
import android.os.Build;
@@ -25,6 +26,7 @@
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
@@ -41,26 +43,26 @@
/* Parse fallback list (no names) */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static FontConfig parse(InputStream in) throws XmlPullParserException, IOException {
- return parse(in, "/system/fonts");
+ return parse(in, "/system/fonts", null);
}
/**
* Parse the fonts.xml
*/
- public static FontConfig parse(InputStream in, String fontDir)
- throws XmlPullParserException, IOException {
+ public static FontConfig parse(InputStream in, String fontDir,
+ @Nullable String updatableFontDir) throws XmlPullParserException, IOException {
try {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(in, null);
parser.nextTag();
- return readFamilies(parser, fontDir);
+ return readFamilies(parser, fontDir, updatableFontDir);
} finally {
in.close();
}
}
- private static FontConfig readFamilies(XmlPullParser parser, String fontDir)
- throws XmlPullParserException, IOException {
+ private static FontConfig readFamilies(XmlPullParser parser, String fontDir,
+ @Nullable String updatableFontDir) throws XmlPullParserException, IOException {
List<FontConfig.Family> families = new ArrayList<>();
List<FontConfig.Alias> aliases = new ArrayList<>();
@@ -69,7 +71,7 @@
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
String tag = parser.getName();
if (tag.equals("family")) {
- families.add(readFamily(parser, fontDir));
+ families.add(readFamily(parser, fontDir, updatableFontDir));
} else if (tag.equals("alias")) {
aliases.add(readAlias(parser));
} else {
@@ -83,8 +85,8 @@
/**
* Reads a family element
*/
- public static FontConfig.Family readFamily(XmlPullParser parser, String fontDir)
- throws XmlPullParserException, IOException {
+ public static FontConfig.Family readFamily(XmlPullParser parser, String fontDir,
+ @Nullable String updatableFontDir) throws XmlPullParserException, IOException {
final String name = parser.getAttributeValue(null, "name");
final String lang = parser.getAttributeValue("", "lang");
final String variant = parser.getAttributeValue(null, "variant");
@@ -93,7 +95,7 @@
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
final String tag = parser.getName();
if (tag.equals("font")) {
- fonts.add(readFont(parser, fontDir));
+ fonts.add(readFont(parser, fontDir, updatableFontDir));
} else {
skip(parser);
}
@@ -114,8 +116,8 @@
private static final Pattern FILENAME_WHITESPACE_PATTERN =
Pattern.compile("^[ \\n\\r\\t]+|[ \\n\\r\\t]+$");
- private static FontConfig.Font readFont(XmlPullParser parser, String fontDir)
- throws XmlPullParserException, IOException {
+ private static FontConfig.Font readFont(XmlPullParser parser, String fontDir,
+ @Nullable String updatableFontDir) throws XmlPullParserException, IOException {
String indexStr = parser.getAttributeValue(null, "index");
int index = indexStr == null ? 0 : Integer.parseInt(indexStr);
List<FontVariationAxis> axes = new ArrayList<FontVariationAxis>();
@@ -137,10 +139,22 @@
}
}
String sanitizedName = FILENAME_WHITESPACE_PATTERN.matcher(filename).replaceAll("");
- return new FontConfig.Font(fontDir + sanitizedName, index, axes.toArray(
+ String fontName = findFontFile(sanitizedName, fontDir, updatableFontDir);
+ return new FontConfig.Font(fontName, index, axes.toArray(
new FontVariationAxis[axes.size()]), weight, isItalic, fallbackFor);
}
+ private static String findFontFile(String fileName, String fontDir,
+ @Nullable String updatableFontDir) {
+ if (updatableFontDir != null) {
+ String updatableFontName = updatableFontDir + fileName;
+ if (new File(updatableFontName).exists()) {
+ return updatableFontName;
+ }
+ }
+ return fontDir + fileName;
+ }
+
private static FontVariationAxis readAxis(XmlPullParser parser)
throws XmlPullParserException, IOException {
String tagStr = parser.getAttributeValue(null, "tag");
diff --git a/graphics/java/android/graphics/fonts/FontCustomizationParser.java b/graphics/java/android/graphics/fonts/FontCustomizationParser.java
index 0291d74..f95da82 100644
--- a/graphics/java/android/graphics/fonts/FontCustomizationParser.java
+++ b/graphics/java/android/graphics/fonts/FontCustomizationParser.java
@@ -97,7 +97,7 @@
throw new IllegalArgumentException("customizationType must be specified");
}
if (customizationType.equals("new-named-family")) {
- out.mAdditionalNamedFamilies.add(FontListParser.readFamily(parser, fontDir));
+ out.mAdditionalNamedFamilies.add(FontListParser.readFamily(parser, fontDir, null));
} else {
throw new IllegalArgumentException("Unknown customizationType=" + customizationType);
}
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index fb6ea99..16a53c2 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -91,7 +91,9 @@
final FontCustomizationParser.Result oemCustomization =
readFontCustomization("/product/etc/fonts_customization.xml", "/product/fonts/");
Map<String, FontFamily[]> map = new ArrayMap<>();
- buildSystemFallback("/system/etc/fonts.xml", "/system/fonts/", oemCustomization, map);
+ // TODO: use updated fonts
+ buildSystemFallback("/system/etc/fonts.xml", "/system/fonts/", null /* updatableFontDir */,
+ oemCustomization, map);
Set<Font> res = new HashSet<>();
for (FontFamily[] families : map.values()) {
for (FontFamily family : families) {
@@ -226,13 +228,7 @@
}
/**
- * Build the system fallback from xml file.
- *
- * @param xmlPath A full path string to the fonts.xml file.
- * @param fontDir A full path string to the system font directory. This must end with
- * slash('/').
- * @param fallbackMap An output system fallback map. Caller must pass empty map.
- * @return a list of aliases
+ * @see #buildSystemFallback(String, String, String, FontCustomizationParser.Result, Map)
* @hide
*/
@VisibleForTesting
@@ -240,9 +236,31 @@
@NonNull String fontDir,
@NonNull FontCustomizationParser.Result oemCustomization,
@NonNull Map<String, FontFamily[]> fallbackMap) {
+ return buildSystemFallback(xmlPath, fontDir, null /* updatableFontDir */,
+ oemCustomization, fallbackMap);
+ }
+
+ /**
+ * Build the system fallback from xml file.
+ *
+ * @param xmlPath A full path string to the fonts.xml file.
+ * @param fontDir A full path string to the system font directory. This must end with
+ * slash('/').
+ * @param updatableFontDir A full path string to the updatable system font directory. This
+ * must end with slash('/').
+ * @param fallbackMap An output system fallback map. Caller must pass empty map.
+ * @return a list of aliases
+ * @hide
+ */
+ @VisibleForTesting
+ public static FontConfig.Alias[] buildSystemFallback(@NonNull String xmlPath,
+ @NonNull String fontDir,
+ @Nullable String updatableFontDir,
+ @NonNull FontCustomizationParser.Result oemCustomization,
+ @NonNull Map<String, FontFamily[]> fallbackMap) {
try {
final FileInputStream fontsIn = new FileInputStream(xmlPath);
- final FontConfig fontConfig = FontListParser.parse(fontsIn, fontDir);
+ final FontConfig fontConfig = FontListParser.parse(fontsIn, fontDir, updatableFontDir);
final HashMap<String, ByteBuffer> bufferCache = new HashMap<String, ByteBuffer>();
final FontConfig.Family[] xmlFamilies = fontConfig.getFamilies();
@@ -306,11 +324,17 @@
/** @hide */
public static @NonNull Pair<FontConfig.Alias[], Map<String, FontFamily[]>>
initializePreinstalledFonts() {
+ return initializeSystemFonts(null);
+ }
+
+ /** @hide */
+ public static Pair<FontConfig.Alias[], Map<String, FontFamily[]>>
+ initializeSystemFonts(@Nullable String updatableFontDir) {
final FontCustomizationParser.Result oemCustomization =
readFontCustomization("/product/etc/fonts_customization.xml", "/product/fonts/");
Map<String, FontFamily[]> map = new ArrayMap<>();
FontConfig.Alias[] aliases = buildSystemFallback("/system/etc/fonts.xml", "/system/fonts/",
- oemCustomization, map);
+ updatableFontDir, oemCustomization, map);
synchronized (LOCK) {
sFamilyMap = map;
}
diff --git a/identity/java/android/security/identity/Util.java b/identity/java/android/security/identity/Util.java
index 6eefeb8..e56bd51 100644
--- a/identity/java/android/security/identity/Util.java
+++ b/identity/java/android/security/identity/Util.java
@@ -16,6 +16,8 @@
package android.security.identity;
+import android.annotation.NonNull;
+
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.InvalidKeyException;
@@ -28,7 +30,10 @@
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
-class Util {
+/**
+ * @hide
+ */
+public class Util {
private static final String TAG = "Util";
static int[] integerCollectionToArray(Collection<Integer> collection) {
@@ -91,8 +96,9 @@
* 255.DigestSize, where DigestSize is the size of the underlying HMAC.
* @return size pseudorandom bytes.
*/
- static byte[] computeHkdf(
- String macAlgorithm, final byte[] ikm, final byte[] salt, final byte[] info, int size) {
+ @NonNull public static byte[] computeHkdf(
+ @NonNull String macAlgorithm, @NonNull final byte[] ikm, @NonNull final byte[] salt,
+ @NonNull final byte[] info, int size) {
Mac mac = null;
try {
mac = Mac.getInstance(macAlgorithm);
@@ -137,4 +143,5 @@
}
}
+ private Util() {}
}
diff --git a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
index 4070829..0290d9f 100644
--- a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
+++ b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
@@ -7,24 +7,12 @@
"group": "WM_SHELL_TASK_ORG",
"at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
},
- "-1534364071": {
- "message": "onTransitionReady %s: %s",
- "level": "VERBOSE",
- "group": "WM_SHELL_TRANSITIONS",
- "at": "com\/android\/wm\/shell\/Transitions.java"
- },
"-1501874464": {
"message": "Fullscreen Task Appeared: #%d",
"level": "VERBOSE",
"group": "WM_SHELL_TASK_ORG",
"at": "com\/android\/wm\/shell\/FullscreenTaskListener.java"
},
- "-1480787369": {
- "message": "Transition requested: type=%d %s",
- "level": "VERBOSE",
- "group": "WM_SHELL_TRANSITIONS",
- "at": "com\/android\/wm\/shell\/Transitions.java"
- },
"-1382704050": {
"message": "Display removed: %d",
"level": "VERBOSE",
@@ -97,12 +85,6 @@
"group": "WM_SHELL_TASK_ORG",
"at": "com\/android\/wm\/shell\/apppairs\/AppPairsController.java"
},
- "-191422040": {
- "message": "Transition animations finished, notifying core %s",
- "level": "VERBOSE",
- "group": "WM_SHELL_TRANSITIONS",
- "at": "com\/android\/wm\/shell\/Transitions.java"
- },
"157713005": {
"message": "Task info changed taskId=%d",
"level": "VERBOSE",
@@ -115,6 +97,12 @@
"group": "WM_SHELL_DRAG_AND_DROP",
"at": "com\/android\/wm\/shell\/draganddrop\/DropOutlineDrawable.java"
},
+ "325110414": {
+ "message": "Transition animations finished, notifying core %s",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_TRANSITIONS",
+ "at": "com\/android\/wm\/shell\/transition\/Transitions.java"
+ },
"375908576": {
"message": "Clip description: handlingDrag=%b itemCount=%d mimeTypes=%s",
"level": "VERBOSE",
@@ -145,6 +133,12 @@
"group": "WM_SHELL_TASK_ORG",
"at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
},
+ "846958769": {
+ "message": "Transition requested: type=%d %s",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_TRANSITIONS",
+ "at": "com\/android\/wm\/shell\/transition\/Transitions.java"
+ },
"900599280": {
"message": "Can't pair unresizeable tasks task1.isResizeable=%b task1.isResizeable=%b",
"level": "ERROR",
@@ -169,6 +163,12 @@
"group": "WM_SHELL_TASK_ORG",
"at": "com\/android\/wm\/shell\/legacysplitscreen\/LegacySplitScreenTaskListener.java"
},
+ "1070270131": {
+ "message": "onTransitionReady %s: %s",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_TRANSITIONS",
+ "at": "com\/android\/wm\/shell\/transition\/Transitions.java"
+ },
"1079041527": {
"message": "incrementPool size=%d",
"level": "VERBOSE",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
index 8817f8a..0146b72 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
@@ -30,6 +30,7 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
index 0056761..0cee0a2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
@@ -24,9 +24,9 @@
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+import com.android.wm.shell.transition.Transitions;
import java.util.Optional;
-import java.util.concurrent.TimeUnit;
/**
* The entry point implementation into the shell for initializing shell internal state.
@@ -41,6 +41,7 @@
private final Optional<AppPairs> mAppPairsOptional;
private final FullscreenTaskListener mFullscreenTaskListener;
private final ShellExecutor mMainExecutor;
+ private final Transitions mTransitions;
private final InitImpl mImpl = new InitImpl();
@@ -50,6 +51,7 @@
Optional<LegacySplitScreen> legacySplitScreenOptional,
Optional<AppPairs> appPairsOptional,
FullscreenTaskListener fullscreenTaskListener,
+ Transitions transitions,
ShellExecutor mainExecutor) {
return new ShellInitImpl(displayImeController,
dragAndDropController,
@@ -57,6 +59,7 @@
legacySplitScreenOptional,
appPairsOptional,
fullscreenTaskListener,
+ transitions,
mainExecutor).mImpl;
}
@@ -66,6 +69,7 @@
Optional<LegacySplitScreen> legacySplitScreenOptional,
Optional<AppPairs> appPairsOptional,
FullscreenTaskListener fullscreenTaskListener,
+ Transitions transitions,
ShellExecutor mainExecutor) {
mDisplayImeController = displayImeController;
mDragAndDropController = dragAndDropController;
@@ -73,6 +77,7 @@
mLegacySplitScreenOptional = legacySplitScreenOptional;
mAppPairsOptional = appPairsOptional;
mFullscreenTaskListener = fullscreenTaskListener;
+ mTransitions = transitions;
mMainExecutor = mainExecutor;
}
@@ -89,6 +94,10 @@
// Bind the splitscreen impl to the drag drop controller
mDragAndDropController.initialize(mLegacySplitScreenOptional);
+
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mTransitions.register(mShellTaskOrganizer);
+ }
}
@ExternalThread
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
index fa0a75c..4874d3c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.os.Handler;
+import android.os.Looper;
/** Executor implementation which is backed by a Handler. */
public class HandlerExecutor implements ShellExecutor {
@@ -49,4 +50,14 @@
public void removeCallbacks(@NonNull Runnable r) {
mHandler.removeCallbacks(r);
}
+
+ @Override
+ public boolean hasCallback(Runnable r) {
+ return mHandler.hasCallbacks(r);
+ }
+
+ @Override
+ public Looper getLooper() {
+ return mHandler.getLooper();
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
index f2c57f7..d37e628 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.common;
+import android.os.Looper;
+
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
@@ -69,4 +71,14 @@
* See {@link android.os.Handler#removeCallbacks}.
*/
void removeCallbacks(Runnable r);
+
+ /**
+ * See {@link android.os.Handler#hasCallbacks(Runnable)}.
+ */
+ boolean hasCallback(Runnable r);
+
+ /**
+ * Returns the looper that this executor is running on.
+ */
+ Looper getLooper();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
index 9e1f0a2..a785cff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
@@ -43,7 +43,6 @@
import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.Transitions;
import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
@@ -54,6 +53,7 @@
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java
index 3ed070b..5a493c2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java
@@ -38,8 +38,8 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.Transitions;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/SplitScreenTransitions.java
index 93520c0..94b2cc0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/SplitScreenTransitions.java
@@ -38,9 +38,9 @@
import android.window.TransitionInfo;
import android.window.WindowContainerTransaction;
-import com.android.wm.shell.Transitions;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.transition.Transitions;
import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java
index 90a8de0..a8cd1dd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java
@@ -18,7 +18,10 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
@@ -39,8 +42,8 @@
import android.window.WindowOrganizer;
import com.android.internal.annotations.GuardedBy;
-import com.android.wm.shell.Transitions;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.transition.Transitions;
import java.util.ArrayList;
import java.util.List;
@@ -54,6 +57,17 @@
private static final String TAG = "WindowManagerProxy";
private static final int[] HOME_AND_RECENTS = {ACTIVITY_TYPE_HOME, ACTIVITY_TYPE_RECENTS};
+ private static final int[] CONTROLLED_ACTIVITY_TYPES = {
+ ACTIVITY_TYPE_STANDARD,
+ ACTIVITY_TYPE_HOME,
+ ACTIVITY_TYPE_RECENTS,
+ ACTIVITY_TYPE_UNDEFINED
+ };
+ private static final int[] CONTROLLED_WINDOWING_MODES = {
+ WINDOWING_MODE_FULLSCREEN,
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
+ WINDOWING_MODE_UNDEFINED
+ };
@GuardedBy("mDockedRect")
private final Rect mDockedRect = new Rect();
@@ -191,8 +205,9 @@
// Set launchtile first so that any stack created after
// getAllRootTaskInfos and before reparent (even if unlikely) are placed
// correctly.
- mTaskOrganizer.setLaunchRoot(DEFAULT_DISPLAY, tiles.mSecondary.token);
WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setLaunchRoot(tiles.mSecondary.token, CONTROLLED_WINDOWING_MODES,
+ CONTROLLED_ACTIVITY_TYPES);
final boolean isHomeResizable = buildEnterSplit(wct, tiles, layout);
applySyncTransaction(wct);
return isHomeResizable;
@@ -251,12 +266,12 @@
/** @see #buildDismissSplit */
void applyDismissSplit(LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout,
boolean dismissOrMaximize) {
- // Set launch root first so that any task created after getChildContainers and
- // before reparent (pretty unlikely) are put into fullscreen.
- mTaskOrganizer.setLaunchRoot(Display.DEFAULT_DISPLAY, null);
// TODO(task-org): Once task-org is more complete, consider using Appeared/Vanished
// plus specific APIs to clean this up.
final WindowContainerTransaction wct = new WindowContainerTransaction();
+ // Set launch root first so that any task created after getChildContainers and
+ // before reparent (pretty unlikely) are put into fullscreen.
+ wct.setLaunchRoot(tiles.mSecondary.token, null, null);
buildDismissSplit(wct, tiles, layout, dismissOrMaximize);
applySyncTransaction(wct);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java
index 146f231..d22abe4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java
@@ -127,7 +127,6 @@
mSurfaceControlTransactionFactory;
private @TransitionDirection int mTransitionDirection;
- private int mTransitionOffset;
private OneHandedTransitionAnimator(SurfaceControl leash, T startValue, T endValue) {
mLeash = leash;
@@ -231,11 +230,6 @@
return this;
}
- OneHandedTransitionAnimator<T> setTransitionOffset(int offset) {
- mTransitionOffset = offset;
- return this;
- }
-
T getStartValue() {
return mStartValue;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 00605d8..48d6a7b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -26,7 +26,6 @@
import android.database.ContentObserver;
import android.graphics.Point;
import android.os.Handler;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
@@ -41,8 +40,10 @@
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.onehanded.OneHandedGestureHandler.OneHandedGestureEventCallback;
import java.io.PrintWriter;
@@ -51,7 +52,7 @@
/**
* Manages and manipulates the one handed states, transitions, and gesture for phones.
*/
-public class OneHandedController implements OneHanded {
+public class OneHandedController {
private static final String TAG = "OneHandedController";
private static final String ONE_HANDED_MODE_OFFSET_PERCENTAGE =
@@ -61,8 +62,8 @@
static final String SUPPORT_ONE_HANDED_MODE = "ro.support_one_handed_mode";
- private boolean mIsOneHandedEnabled;
- private boolean mIsSwipeToNotificationEnabled;
+ private volatile boolean mIsOneHandedEnabled;
+ private volatile boolean mIsSwipeToNotificationEnabled;
private boolean mTaskChangeToExit;
private float mOffSetFraction;
@@ -73,7 +74,9 @@
private final OneHandedTouchHandler mTouchHandler;
private final OneHandedTutorialHandler mTutorialHandler;
private final IOverlayManager mOverlayManager;
- private final Handler mMainHandler = new Handler(Looper.getMainLooper());
+ private final ShellExecutor mMainExecutor;
+ private final Handler mMainHandler;
+ private final OneHandedImpl mImpl = new OneHandedImpl();
private OneHandedDisplayAreaOrganizer mDisplayAreaOrganizer;
private final AccessibilityManager mAccessibilityManager;
@@ -89,83 +92,10 @@
}
};
- private final ContentObserver mEnabledObserver = new ContentObserver(mMainHandler) {
- @Override
- public void onChange(boolean selfChange) {
- final boolean enabled = OneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
- mContext.getContentResolver());
- OneHandedEvents.writeEvent(enabled
- ? OneHandedEvents.EVENT_ONE_HANDED_SETTINGS_ENABLED_ON
- : OneHandedEvents.EVENT_ONE_HANDED_SETTINGS_ENABLED_OFF);
-
- setOneHandedEnabled(enabled);
-
- // Also checks swipe to notification settings since they all need gesture overlay.
- setEnabledGesturalOverlay(
- enabled || OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
- mContext.getContentResolver()));
- }
- };
-
- private final ContentObserver mTimeoutObserver = new ContentObserver(mMainHandler) {
- @Override
- public void onChange(boolean selfChange) {
- final int newTimeout = OneHandedSettingsUtil.getSettingsOneHandedModeTimeout(
- mContext.getContentResolver());
- int metricsId = OneHandedEvents.OneHandedSettingsTogglesEvent.INVALID.getId();
- switch (newTimeout) {
- case OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_NEVER:
- metricsId = OneHandedEvents.EVENT_ONE_HANDED_SETTINGS_TIMEOUT_SECONDS_NEVER;
- break;
- case OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_SHORT_IN_SECONDS:
- metricsId = OneHandedEvents.EVENT_ONE_HANDED_SETTINGS_TIMEOUT_SECONDS_4;
- break;
- case OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS:
- metricsId = OneHandedEvents.EVENT_ONE_HANDED_SETTINGS_TIMEOUT_SECONDS_8;
- break;
- case OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_LONG_IN_SECONDS:
- metricsId = OneHandedEvents.EVENT_ONE_HANDED_SETTINGS_TIMEOUT_SECONDS_12;
- break;
- default:
- // do nothing
- break;
- }
- OneHandedEvents.writeEvent(metricsId);
-
- if (mTimeoutHandler != null) {
- mTimeoutHandler.setTimeout(newTimeout);
- }
- }
- };
-
- private final ContentObserver mTaskChangeExitObserver = new ContentObserver(mMainHandler) {
- @Override
- public void onChange(boolean selfChange) {
- final boolean enabled = OneHandedSettingsUtil.getSettingsTapsAppToExit(
- mContext.getContentResolver());
- OneHandedEvents.writeEvent(enabled
- ? OneHandedEvents.EVENT_ONE_HANDED_SETTINGS_APP_TAPS_EXIT_ON
- : OneHandedEvents.EVENT_ONE_HANDED_SETTINGS_APP_TAPS_EXIT_OFF);
-
- setTaskChangeToExit(enabled);
- }
- };
-
- private final ContentObserver mSwipeToNotificationEnabledObserver =
- new ContentObserver(mMainHandler) {
- @Override
- public void onChange(boolean selfChange) {
- final boolean enabled =
- OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
- mContext.getContentResolver());
- setSwipeToNotificationEnabled(enabled);
-
- // Also checks one handed mode settings since they all need gesture overlay.
- setEnabledGesturalOverlay(
- enabled || OneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
- mContext.getContentResolver()));
- }
- };
+ private final ContentObserver mEnabledObserver;
+ private final ContentObserver mTimeoutObserver;
+ private final ContentObserver mTaskChangeExitObserver;
+ private final ContentObserver mSwipeToNotificationEnabledObserver;
private AccessibilityManager.AccessibilityStateChangeListener
mAccessibilityStateChangeListener =
@@ -188,33 +118,39 @@
};
/**
- * Creates {@link OneHandedController}, returns {@code null} if the feature is not supported.
+ * Creates {@link OneHanded}, returns {@code null} if the feature is not supported.
*/
@Nullable
- public static OneHandedController create(
+ public static OneHanded create(
Context context, DisplayController displayController,
- TaskStackListenerImpl taskStackListener, Executor executor) {
+ TaskStackListenerImpl taskStackListener,
+ ShellExecutor mainExecutor,
+ Handler mainHandler) {
if (!SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false)) {
Slog.w(TAG, "Device doesn't support OneHanded feature");
return null;
}
- OneHandedTutorialHandler tutorialHandler = new OneHandedTutorialHandler(context);
+ OneHandedTimeoutHandler timeoutHandler = new OneHandedTimeoutHandler(mainExecutor);
+ OneHandedTutorialHandler tutorialHandler = new OneHandedTutorialHandler(context,
+ mainExecutor);
OneHandedAnimationController animationController =
new OneHandedAnimationController(context);
- OneHandedTouchHandler touchHandler = new OneHandedTouchHandler();
+ OneHandedTouchHandler touchHandler = new OneHandedTouchHandler(timeoutHandler,
+ mainExecutor);
OneHandedGestureHandler gestureHandler = new OneHandedGestureHandler(
- context, displayController);
+ context, displayController, mainExecutor);
OneHandedBackgroundPanelOrganizer oneHandedBackgroundPanelOrganizer =
- new OneHandedBackgroundPanelOrganizer(context, displayController, executor);
+ new OneHandedBackgroundPanelOrganizer(context, displayController, mainExecutor);
OneHandedDisplayAreaOrganizer organizer = new OneHandedDisplayAreaOrganizer(
- context, displayController, animationController, tutorialHandler, executor,
- oneHandedBackgroundPanelOrganizer);
+ context, displayController, animationController, tutorialHandler,
+ oneHandedBackgroundPanelOrganizer, mainExecutor);
IOverlayManager overlayManager = IOverlayManager.Stub.asInterface(
ServiceManager.getService(Context.OVERLAY_SERVICE));
return new OneHandedController(context, displayController,
oneHandedBackgroundPanelOrganizer, organizer, touchHandler, tutorialHandler,
- gestureHandler, overlayManager, taskStackListener);
+ gestureHandler, timeoutHandler, overlayManager, taskStackListener, mainExecutor,
+ mainHandler).mImpl;
}
@VisibleForTesting
@@ -225,8 +161,11 @@
OneHandedTouchHandler touchHandler,
OneHandedTutorialHandler tutorialHandler,
OneHandedGestureHandler gestureHandler,
+ OneHandedTimeoutHandler timeoutHandler,
IOverlayManager overlayManager,
- TaskStackListenerImpl taskStackListener) {
+ TaskStackListenerImpl taskStackListener,
+ ShellExecutor mainExecutor,
+ Handler mainHandler) {
mContext = context;
mBackgroundPanelOrganizer = backgroundPanelOrganizer;
mDisplayAreaOrganizer = displayAreaOrganizer;
@@ -235,6 +174,8 @@
mTutorialHandler = tutorialHandler;
mGestureHandler = gestureHandler;
mOverlayManager = overlayManager;
+ mMainExecutor = mainExecutor;
+ mMainHandler = mainHandler;
final float offsetPercentageConfig = context.getResources().getFraction(
R.fraction.config_one_handed_offset, 1, 1);
@@ -246,7 +187,13 @@
mIsSwipeToNotificationEnabled =
OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
context.getContentResolver());
- mTimeoutHandler = OneHandedTimeoutHandler.get();
+ mTimeoutHandler = timeoutHandler;
+
+ mEnabledObserver = getObserver(this::onEnabledSettingChanged);
+ mTimeoutObserver = getObserver(this::onTimeoutSettingChanged);
+ mTaskChangeExitObserver = getObserver(this::onTaskChangeExitSettingChanged);
+ mSwipeToNotificationEnabledObserver =
+ getObserver(this::onSwipeToNotificationEnabledSettingChanged);
mDisplayController.addDisplayChangingController(mRotationController);
@@ -298,18 +245,8 @@
updateOneHandedEnabled();
}
- @Override
- public boolean isOneHandedEnabled() {
- return mIsOneHandedEnabled;
- }
-
- @Override
- public boolean isSwipeToNotificationEnabled() {
- return mIsSwipeToNotificationEnabled;
- }
-
- @Override
- public void startOneHanded() {
+ @VisibleForTesting
+ void startOneHanded() {
if (!mDisplayAreaOrganizer.isInOneHanded()) {
final int yOffSet = Math.round(getDisplaySize().y * mOffSetFraction);
mDisplayAreaOrganizer.scheduleOffset(0, yOffSet);
@@ -319,16 +256,15 @@
}
}
- @Override
- public void stopOneHanded() {
+ @VisibleForTesting
+ void stopOneHanded() {
if (mDisplayAreaOrganizer.isInOneHanded()) {
mDisplayAreaOrganizer.scheduleOffset(0, 0);
mTimeoutHandler.removeTimer();
}
}
- @Override
- public void stopOneHanded(int event) {
+ private void stopOneHanded(int event) {
if (!mTaskChangeToExit && event == OneHandedEvents.EVENT_ONE_HANDED_TRIGGER_APP_TAPS_OUT) {
//Task change exit not enable, do nothing and return here.
return;
@@ -341,18 +277,16 @@
stopOneHanded();
}
- @Override
- public void setThreeButtonModeEnabled(boolean enabled) {
+ private void setThreeButtonModeEnabled(boolean enabled) {
mGestureHandler.onThreeButtonModeEnabled(enabled);
}
- @Override
- public void registerTransitionCallback(OneHandedTransitionCallback callback) {
+ @VisibleForTesting
+ void registerTransitionCallback(OneHandedTransitionCallback callback) {
mDisplayAreaOrganizer.registerTransitionCallback(callback);
}
- @Override
- public void registerGestureCallback(OneHandedGestureEventCallback callback) {
+ private void registerGestureCallback(OneHandedGestureEventCallback callback) {
mGestureHandler.setGestureEventListener(callback);
}
@@ -388,6 +322,80 @@
.getSettingsSwipeToNotificationEnabled(mContext.getContentResolver()));
}
+ private ContentObserver getObserver(Runnable onChangeRunnable) {
+ return new ContentObserver(mMainHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ onChangeRunnable.run();
+ }
+ };
+ }
+
+ private void onEnabledSettingChanged() {
+ final boolean enabled = OneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
+ mContext.getContentResolver());
+ OneHandedEvents.writeEvent(enabled
+ ? OneHandedEvents.EVENT_ONE_HANDED_SETTINGS_ENABLED_ON
+ : OneHandedEvents.EVENT_ONE_HANDED_SETTINGS_ENABLED_OFF);
+
+ setOneHandedEnabled(enabled);
+
+ // Also checks swipe to notification settings since they all need gesture overlay.
+ setEnabledGesturalOverlay(
+ enabled || OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
+ mContext.getContentResolver()));
+ }
+
+ private void onTimeoutSettingChanged() {
+ final int newTimeout = OneHandedSettingsUtil.getSettingsOneHandedModeTimeout(
+ mContext.getContentResolver());
+ int metricsId = OneHandedEvents.OneHandedSettingsTogglesEvent.INVALID.getId();
+ switch (newTimeout) {
+ case OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_NEVER:
+ metricsId = OneHandedEvents.EVENT_ONE_HANDED_SETTINGS_TIMEOUT_SECONDS_NEVER;
+ break;
+ case OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_SHORT_IN_SECONDS:
+ metricsId = OneHandedEvents.EVENT_ONE_HANDED_SETTINGS_TIMEOUT_SECONDS_4;
+ break;
+ case OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS:
+ metricsId = OneHandedEvents.EVENT_ONE_HANDED_SETTINGS_TIMEOUT_SECONDS_8;
+ break;
+ case OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_LONG_IN_SECONDS:
+ metricsId = OneHandedEvents.EVENT_ONE_HANDED_SETTINGS_TIMEOUT_SECONDS_12;
+ break;
+ default:
+ // do nothing
+ break;
+ }
+ OneHandedEvents.writeEvent(metricsId);
+
+ if (mTimeoutHandler != null) {
+ mTimeoutHandler.setTimeout(newTimeout);
+ }
+ }
+
+ private void onTaskChangeExitSettingChanged() {
+ final boolean enabled = OneHandedSettingsUtil.getSettingsTapsAppToExit(
+ mContext.getContentResolver());
+ OneHandedEvents.writeEvent(enabled
+ ? OneHandedEvents.EVENT_ONE_HANDED_SETTINGS_APP_TAPS_EXIT_ON
+ : OneHandedEvents.EVENT_ONE_HANDED_SETTINGS_APP_TAPS_EXIT_OFF);
+
+ setTaskChangeToExit(enabled);
+ }
+
+ private void onSwipeToNotificationEnabledSettingChanged() {
+ final boolean enabled =
+ OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
+ mContext.getContentResolver());
+ setSwipeToNotificationEnabled(enabled);
+
+ // Also checks one handed mode settings since they all need gesture overlay.
+ setEnabledGesturalOverlay(
+ enabled || OneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
+ mContext.getContentResolver()));
+ }
+
private void setupTimeoutListener() {
mTimeoutHandler.registerTimeoutListener(timeoutTime -> {
stopOneHanded(OneHandedEvents.EVENT_ONE_HANDED_TRIGGER_TIMEOUT_OUT);
@@ -451,8 +459,7 @@
}
}
- @Override
- public void dump(@NonNull PrintWriter pw) {
+ private void dump(@NonNull PrintWriter pw) {
final String innerPrefix = " ";
pw.println(TAG + "states: ");
pw.print(innerPrefix + "mOffSetFraction=");
@@ -489,4 +496,68 @@
}
}
}
+
+ @ExternalThread
+ private class OneHandedImpl implements OneHanded {
+ @Override
+ public boolean isOneHandedEnabled() {
+ // This is volatile so return directly
+ return mIsOneHandedEnabled;
+ }
+
+ @Override
+ public boolean isSwipeToNotificationEnabled() {
+ // This is volatile so return directly
+ return mIsSwipeToNotificationEnabled;
+ }
+
+ @Override
+ public void startOneHanded() {
+ mMainExecutor.execute(() -> {
+ OneHandedController.this.startOneHanded();
+ });
+ }
+
+ @Override
+ public void stopOneHanded() {
+ mMainExecutor.execute(() -> {
+ OneHandedController.this.stopOneHanded();
+ });
+ }
+
+ @Override
+ public void stopOneHanded(int event) {
+ mMainExecutor.execute(() -> {
+ OneHandedController.this.stopOneHanded(event);
+ });
+ }
+
+ @Override
+ public void setThreeButtonModeEnabled(boolean enabled) {
+ mMainExecutor.execute(() -> {
+ OneHandedController.this.setThreeButtonModeEnabled(enabled);
+ });
+ }
+
+ @Override
+ public void registerTransitionCallback(OneHandedTransitionCallback callback) {
+ mMainExecutor.execute(() -> {
+ OneHandedController.this.registerTransitionCallback(callback);
+ });
+ }
+
+ @Override
+ public void registerGestureCallback(OneHandedGestureEventCallback callback) {
+ mMainExecutor.execute(() -> {
+ OneHandedController.this.registerGestureCallback(callback);
+ });
+ }
+
+ @Override
+ public void dump(@NonNull PrintWriter pw) {
+ mMainExecutor.execute(() -> {
+ OneHandedController.this.dump(pw);
+ });
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
index 7873318..d2d5591 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
@@ -24,8 +24,6 @@
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
-import android.os.Handler;
-import android.os.Looper;
import android.os.SystemProperties;
import android.util.ArrayMap;
import android.util.Log;
@@ -39,9 +37,9 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
-import com.android.internal.os.SomeArgs;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ShellExecutor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -64,17 +62,9 @@
private static final String ONE_HANDED_MODE_TRANSLATE_ANIMATION_DURATION =
"persist.debug.one_handed_translate_animation_duration";
- @VisibleForTesting
- static final int MSG_RESET_IMMEDIATE = 1;
- @VisibleForTesting
- static final int MSG_OFFSET_ANIMATE = 2;
- @VisibleForTesting
- static final int MSG_OFFSET_FINISH = 3;
-
private final Rect mLastVisualDisplayBounds = new Rect();
private final Rect mDefaultDisplayBounds = new Rect();
- private Handler mUpdateHandler;
private boolean mIsInOneHanded;
private int mEnterExitAnimationDurationMs;
@@ -101,8 +91,8 @@
OneHandedAnimationController.OneHandedTransitionAnimator animator) {
mAnimationController.removeAnimator(animator.getLeash());
if (mAnimationController.isAnimatorsConsumed()) {
- mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_OFFSET_FINISH,
- obtainArgsFromAnimator(animator)));
+ finishOffset(animator.getDestinationOffset(),
+ animator.getTransitionDirection());
}
}
@@ -111,52 +101,22 @@
OneHandedAnimationController.OneHandedTransitionAnimator animator) {
mAnimationController.removeAnimator(animator.getLeash());
if (mAnimationController.isAnimatorsConsumed()) {
- mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_OFFSET_FINISH,
- obtainArgsFromAnimator(animator)));
+ finishOffset(animator.getDestinationOffset(),
+ animator.getTransitionDirection());
}
}
};
- @SuppressWarnings("unchecked")
- private Handler.Callback mUpdateCallback = (msg) -> {
- SomeArgs args = (SomeArgs) msg.obj;
- final Rect currentBounds = args.arg1 != null ? (Rect) args.arg1 : mDefaultDisplayBounds;
- final WindowContainerTransaction wctFromRotate = (WindowContainerTransaction) args.arg2;
- final int yOffset = args.argi2;
- final int direction = args.argi3;
-
- switch (msg.what) {
- case MSG_RESET_IMMEDIATE:
- resetWindowsOffset(wctFromRotate);
- mDefaultDisplayBounds.set(currentBounds);
- mLastVisualDisplayBounds.set(currentBounds);
- finishOffset(0, TRANSITION_DIRECTION_EXIT);
- break;
- case MSG_OFFSET_ANIMATE:
- final Rect toBounds = new Rect(mDefaultDisplayBounds.left,
- mDefaultDisplayBounds.top + yOffset,
- mDefaultDisplayBounds.right,
- mDefaultDisplayBounds.bottom + yOffset);
- offsetWindows(currentBounds, toBounds, direction, mEnterExitAnimationDurationMs);
- break;
- case MSG_OFFSET_FINISH:
- finishOffset(yOffset, direction);
- break;
- }
- args.recycle();
- return true;
- };
-
/**
* Constructor of OneHandedDisplayAreaOrganizer
*/
public OneHandedDisplayAreaOrganizer(Context context,
DisplayController displayController,
OneHandedAnimationController animationController,
- OneHandedTutorialHandler tutorialHandler, Executor executor,
- OneHandedBackgroundPanelOrganizer oneHandedBackgroundGradientOrganizer) {
- super(executor);
- mUpdateHandler = new Handler(OneHandedThread.get().getLooper(), mUpdateCallback);
+ OneHandedTutorialHandler tutorialHandler,
+ OneHandedBackgroundPanelOrganizer oneHandedBackgroundGradientOrganizer,
+ ShellExecutor mainExecutor) {
+ super(mainExecutor);
mAnimationController = animationController;
mDisplayController = displayController;
mDefaultDisplayBounds.set(getDisplayBounds());
@@ -176,12 +136,10 @@
@NonNull SurfaceControl leash) {
Objects.requireNonNull(displayAreaInfo, "displayAreaInfo must not be null");
Objects.requireNonNull(leash, "leash must not be null");
- synchronized (this) {
- if (mDisplayAreaMap.get(displayAreaInfo) == null) {
- // mDefaultDisplayBounds may out of date after removeDisplayChangingController()
- mDefaultDisplayBounds.set(getDisplayBounds());
- mDisplayAreaMap.put(displayAreaInfo, leash);
- }
+ if (mDisplayAreaMap.get(displayAreaInfo) == null) {
+ // mDefaultDisplayBounds may out of date after removeDisplayChangingController()
+ mDefaultDisplayBounds.set(getDisplayBounds());
+ mDisplayAreaMap.put(displayAreaInfo, leash);
}
}
@@ -189,13 +147,11 @@
public void onDisplayAreaVanished(@NonNull DisplayAreaInfo displayAreaInfo) {
Objects.requireNonNull(displayAreaInfo,
"Requires valid displayArea, and displayArea must not be null");
- synchronized (this) {
- if (!mDisplayAreaMap.containsKey(displayAreaInfo)) {
- Log.w(TAG, "Unrecognized token: " + displayAreaInfo.token);
- return;
- }
- mDisplayAreaMap.remove(displayAreaInfo);
+ if (!mDisplayAreaMap.containsKey(displayAreaInfo)) {
+ Log.w(TAG, "Unrecognized token: " + displayAreaInfo.token);
+ return;
}
+ mDisplayAreaMap.remove(displayAreaInfo);
}
@Override
@@ -212,7 +168,7 @@
@Override
public void unregisterOrganizer() {
super.unregisterOrganizer();
- mUpdateHandler.post(() -> resetWindowsOffset(null));
+ resetWindowsOffset(null);
}
/**
@@ -230,14 +186,10 @@
final boolean isOrientationDiff = Math.abs(fromRotation - toRotation) % 2 == 1;
if (isOrientationDiff) {
- newBounds.set(newBounds.left, newBounds.top, newBounds.bottom, newBounds.right);
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = newBounds;
- args.arg2 = wct;
- args.argi1 = 0 /* xOffset */;
- args.argi2 = 0 /* yOffset */;
- args.argi3 = TRANSITION_DIRECTION_EXIT;
- mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_RESET_IMMEDIATE, args));
+ resetWindowsOffset(wct);
+ mDefaultDisplayBounds.set(newBounds);
+ mLastVisualDisplayBounds.set(newBounds);
+ finishOffset(0, TRANSITION_DIRECTION_EXIT);
}
}
@@ -246,79 +198,65 @@
* Directly perform manipulation/offset on the leash.
*/
public void scheduleOffset(int xOffset, int yOffset) {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = getLastVisualDisplayBounds();
- args.argi1 = xOffset;
- args.argi2 = yOffset;
- args.argi3 = yOffset > 0 ? TRANSITION_DIRECTION_TRIGGER : TRANSITION_DIRECTION_EXIT;
- mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_OFFSET_ANIMATE, args));
- }
+ final Rect toBounds = new Rect(mDefaultDisplayBounds.left,
+ mDefaultDisplayBounds.top + yOffset,
+ mDefaultDisplayBounds.right,
+ mDefaultDisplayBounds.bottom + yOffset);
+ final Rect fromBounds = getLastVisualDisplayBounds() != null
+ ? getLastVisualDisplayBounds()
+ : mDefaultDisplayBounds;
+ final int direction = yOffset > 0
+ ? TRANSITION_DIRECTION_TRIGGER
+ : TRANSITION_DIRECTION_EXIT;
- private void offsetWindows(Rect fromBounds, Rect toBounds, int direction, int durationMs) {
- if (Looper.myLooper() != mUpdateHandler.getLooper()) {
- throw new RuntimeException("Callers should call scheduleOffset() instead of this "
- + "directly");
- }
- synchronized (this) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- mDisplayAreaMap.forEach(
- (key, leash) -> {
- animateWindows(leash, fromBounds, toBounds, direction, durationMs);
- wct.setBounds(key.token, toBounds);
- });
- applyTransaction(wct);
- }
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ mDisplayAreaMap.forEach(
+ (key, leash) -> {
+ animateWindows(leash, fromBounds, toBounds, direction,
+ mEnterExitAnimationDurationMs);
+ wct.setBounds(key.token, toBounds);
+ });
+ applyTransaction(wct);
}
private void resetWindowsOffset(WindowContainerTransaction wct) {
- synchronized (this) {
- final SurfaceControl.Transaction tx =
- mSurfaceControlTransactionFactory.getTransaction();
- mDisplayAreaMap.forEach(
- (key, leash) -> {
- final OneHandedAnimationController.OneHandedTransitionAnimator animator =
- mAnimationController.getAnimatorMap().remove(leash);
- if (animator != null && animator.isRunning()) {
- animator.cancel();
- }
- tx.setPosition(leash, 0, 0)
- .setWindowCrop(leash, -1/* reset */, -1/* reset */);
- // DisplayRotationController will applyTransaction() after finish rotating
- if (wct != null) {
- wct.setBounds(key.token, null/* reset */);
- }
- });
- tx.apply();
- }
+ final SurfaceControl.Transaction tx =
+ mSurfaceControlTransactionFactory.getTransaction();
+ mDisplayAreaMap.forEach(
+ (key, leash) -> {
+ final OneHandedAnimationController.OneHandedTransitionAnimator animator =
+ mAnimationController.getAnimatorMap().remove(leash);
+ if (animator != null && animator.isRunning()) {
+ animator.cancel();
+ }
+ tx.setPosition(leash, 0, 0)
+ .setWindowCrop(leash, -1/* reset */, -1/* reset */);
+ // DisplayRotationController will applyTransaction() after finish rotating
+ if (wct != null) {
+ wct.setBounds(key.token, null/* reset */);
+ }
+ });
+ tx.apply();
}
private void animateWindows(SurfaceControl leash, Rect fromBounds, Rect toBounds,
@OneHandedAnimationController.TransitionDirection int direction, int durationMs) {
- if (Looper.myLooper() != mUpdateHandler.getLooper()) {
- throw new RuntimeException("Callers should call scheduleOffset() instead of "
- + "this directly");
+ final OneHandedAnimationController.OneHandedTransitionAnimator animator =
+ mAnimationController.getAnimator(leash, fromBounds, toBounds);
+ if (animator != null) {
+ animator.setTransitionDirection(direction)
+ .addOneHandedAnimationCallback(mOneHandedAnimationCallback)
+ .addOneHandedAnimationCallback(mTutorialHandler.getAnimationCallback())
+ .addOneHandedAnimationCallback(
+ mBackgroundPanelOrganizer.getOneHandedAnimationCallback())
+ .setDuration(durationMs)
+ .start();
}
- mUpdateHandler.post(() -> {
- final OneHandedAnimationController.OneHandedTransitionAnimator animator =
- mAnimationController.getAnimator(leash, fromBounds, toBounds);
- if (animator != null) {
- animator.setTransitionDirection(direction)
- .addOneHandedAnimationCallback(mOneHandedAnimationCallback)
- .addOneHandedAnimationCallback(mTutorialHandler.getAnimationCallback())
- .addOneHandedAnimationCallback(
- mBackgroundPanelOrganizer.getOneHandedAnimationCallback())
- .setDuration(durationMs)
- .start();
- }
- });
}
- private void finishOffset(int offset,
+ @VisibleForTesting
+ void finishOffset(int offset,
@OneHandedAnimationController.TransitionDirection int direction) {
- if (Looper.myLooper() != mUpdateHandler.getLooper()) {
- throw new RuntimeException(
- "Callers should call scheduleOffset() instead of this directly.");
- }
// Only finishOffset() can update mIsInOneHanded to ensure the state is handle in sequence,
// the flag *MUST* be updated before dispatch mTransitionCallbacks
mIsInOneHanded = (offset > 0 || direction == TRANSITION_DIRECTION_TRIGGER);
@@ -361,11 +299,6 @@
return new Rect(0, 0, realSize.x, realSize.y);
}
- @VisibleForTesting
- void setUpdateHandler(Handler updateHandler) {
- mUpdateHandler = updateHandler;
- }
-
/**
* Register transition callback
*/
@@ -373,16 +306,6 @@
mTransitionCallbacks.add(callback);
}
- private SomeArgs obtainArgsFromAnimator(
- OneHandedAnimationController.OneHandedTransitionAnimator animator) {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = animator.getDestinationBounds();
- args.argi1 = 0 /* xOffset */;
- args.argi2 = animator.getDestinationOffset();
- args.argi3 = animator.getTransitionDirection();
- return args;
- }
-
void dump(@NonNull PrintWriter pw) {
final String innerPrefix = " ";
pw.println(TAG + "states: ");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java
index aa4ec17..1ed121f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java
@@ -41,6 +41,7 @@
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ShellExecutor;
/**
* The class manage swipe up and down gesture for 3-Button mode navigation,
@@ -70,7 +71,8 @@
InputMonitor mInputMonitor;
@VisibleForTesting
InputEventReceiver mInputEventReceiver;
- private DisplayController mDisplayController;
+ private final DisplayController mDisplayController;
+ private final ShellExecutor mMainExecutor;
@VisibleForTesting
@Nullable
OneHandedGestureEventCallback mGestureEventCallback;
@@ -84,8 +86,10 @@
* @param context {@link Context}
* @param displayController {@link DisplayController}
*/
- public OneHandedGestureHandler(Context context, DisplayController displayController) {
+ public OneHandedGestureHandler(Context context, DisplayController displayController,
+ ShellExecutor mainExecutor) {
mDisplayController = displayController;
+ mMainExecutor = mainExecutor;
displayController.addDisplayChangingController(this);
mNavGestureHeight = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_gesture_larger_height);
@@ -93,6 +97,7 @@
R.dimen.gestures_onehanded_drag_threshold);
final float slop = ViewConfiguration.get(context).getScaledTouchSlop();
mSquaredSlop = slop * slop;
+
updateIsEnabled();
}
@@ -217,7 +222,7 @@
mInputMonitor = InputManager.getInstance().monitorGestureInput(
"onehanded-gesture-offset", DEFAULT_DISPLAY);
mInputEventReceiver = new EventReceiver(
- mInputMonitor.getInputChannel(), Looper.getMainLooper());
+ mInputMonitor.getInputChannel(), mMainExecutor.getLooper());
}
}
@@ -233,6 +238,7 @@
mRotation = toRotation;
}
+ // TODO: Use BatchedInputEventReceiver
private class EventReceiver extends InputEventReceiver {
EventReceiver(InputChannel channel, Looper looper) {
super(channel, looper);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedThread.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedThread.java
deleted file mode 100644
index 24d33ed..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedThread.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.onehanded;
-
-import android.os.Handler;
-import android.os.HandlerThread;
-
-/**
- * Similar to {@link com.android.internal.os.BackgroundThread}, this is a shared singleton
- * foreground thread for each process for updating one handed.
- */
-public class OneHandedThread extends HandlerThread {
- private static OneHandedThread sInstance;
- private static Handler sHandler;
-
- private OneHandedThread() {
- super("OneHanded");
- }
-
- private static void ensureThreadLocked() {
- if (sInstance == null) {
- sInstance = new OneHandedThread();
- sInstance.start();
- sHandler = new Handler(sInstance.getLooper());
- }
- }
-
- /**
- * @return the static update thread instance
- */
- public static OneHandedThread get() {
- synchronized (OneHandedThread.class) {
- ensureThreadLocked();
- return sInstance;
- }
- }
-
- /**
- * @return the static update thread handler instance
- */
- public static Handler getHandler() {
- synchronized (OneHandedThread.class) {
- ensureThreadLocked();
- return sHandler;
- }
- }
-
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTimeoutHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTimeoutHandler.java
index 9c97cd7..4a98941 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTimeoutHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTimeoutHandler.java
@@ -19,12 +19,12 @@
import static com.android.wm.shell.onehanded.OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS;
import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
+import com.android.wm.shell.common.ShellExecutor;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -35,18 +35,15 @@
*/
public class OneHandedTimeoutHandler {
private static final String TAG = "OneHandedTimeoutHandler";
- private static boolean sIsDragging = false;
- // Default timeout is ONE_HANDED_TIMEOUT_MEDIUM
- private static @OneHandedSettingsUtil.OneHandedTimeout int sTimeout =
- ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS;
- private static long sTimeoutMs = TimeUnit.SECONDS.toMillis(sTimeout);
- private static OneHandedTimeoutHandler sInstance;
- private static List<TimeoutListener> sListeners = new ArrayList<>();
- @VisibleForTesting
- static final int ONE_HANDED_TIMEOUT_STOP_MSG = 1;
- @VisibleForTesting
- static Handler sHandler;
+ private final ShellExecutor mMainExecutor;
+
+ // Default timeout is ONE_HANDED_TIMEOUT_MEDIUM
+ private @OneHandedSettingsUtil.OneHandedTimeout int mTimeout =
+ ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS;
+ private long mTimeoutMs = TimeUnit.SECONDS.toMillis(mTimeout);
+ private final Runnable mTimeoutRunnable = this::onStop;
+ private List<TimeoutListener> mListeners = new ArrayList<>();
/**
* Get the current config of timeout
@@ -54,7 +51,7 @@
* @return timeout of current config
*/
public @OneHandedSettingsUtil.OneHandedTimeout int getTimeout() {
- return sTimeout;
+ return mTimeout;
}
/**
@@ -69,32 +66,36 @@
void onTimeout(int timeoutTime);
}
+ public OneHandedTimeoutHandler(ShellExecutor mainExecutor) {
+ mMainExecutor = mainExecutor;
+ }
+
/**
* Set the specific timeout of {@link OneHandedSettingsUtil.OneHandedTimeout}
*/
- public static void setTimeout(@OneHandedSettingsUtil.OneHandedTimeout int timeout) {
- sTimeout = timeout;
- sTimeoutMs = TimeUnit.SECONDS.toMillis(sTimeout);
+ public void setTimeout(@OneHandedSettingsUtil.OneHandedTimeout int timeout) {
+ mTimeout = timeout;
+ mTimeoutMs = TimeUnit.SECONDS.toMillis(mTimeout);
resetTimer();
}
/**
* Reset the timer when one handed trigger or user is operating in some conditions
*/
- public static void removeTimer() {
- sHandler.removeMessages(ONE_HANDED_TIMEOUT_STOP_MSG);
+ public void removeTimer() {
+ mMainExecutor.removeCallbacks(mTimeoutRunnable);
}
/**
* Reset the timer when one handed trigger or user is operating in some conditions
*/
- public static void resetTimer() {
+ public void resetTimer() {
removeTimer();
- if (sTimeout == OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_NEVER) {
+ if (mTimeout == OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_NEVER) {
return;
}
- if (sTimeout != OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_NEVER) {
- sHandler.sendEmptyMessageDelayed(ONE_HANDED_TIMEOUT_STOP_MSG, sTimeoutMs);
+ if (mTimeout != OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_NEVER) {
+ mMainExecutor.executeDelayed(mTimeoutRunnable, mTimeoutMs);
}
}
@@ -103,47 +104,19 @@
*
* @param listener the listener be sent events when times up
*/
- public static void registerTimeoutListener(TimeoutListener listener) {
- sListeners.add(listener);
+ public void registerTimeoutListener(TimeoutListener listener) {
+ mListeners.add(listener);
}
- /**
- * Private constructor due to Singleton pattern
- */
- private OneHandedTimeoutHandler() {
+ @VisibleForTesting
+ boolean hasScheduledTimeout() {
+ return mMainExecutor.hasCallback(mTimeoutRunnable);
}
- /**
- * Singleton pattern to get {@link OneHandedTimeoutHandler} instance
- *
- * @return the static update thread instance
- */
- public static OneHandedTimeoutHandler get() {
- synchronized (OneHandedTimeoutHandler.class) {
- if (sInstance == null) {
- sInstance = new OneHandedTimeoutHandler();
- }
- if (sHandler == null) {
- sHandler = new Handler(Looper.myLooper()) {
- @Override
- public void handleMessage(Message msg) {
- if (msg.what == ONE_HANDED_TIMEOUT_STOP_MSG) {
- onStop();
- }
- }
- };
- if (sTimeout != OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_NEVER) {
- sHandler.sendEmptyMessageDelayed(ONE_HANDED_TIMEOUT_STOP_MSG, sTimeoutMs);
- }
- }
- return sInstance;
- }
- }
-
- private static void onStop() {
- for (int i = sListeners.size() - 1; i >= 0; i--) {
- final TimeoutListener listener = sListeners.get(i);
- listener.onTimeout(sTimeout);
+ private void onStop() {
+ for (int i = mListeners.size() - 1; i >= 0; i--) {
+ final TimeoutListener listener = mListeners.get(i);
+ listener.onTimeout(mTimeout);
}
}
@@ -151,9 +124,9 @@
final String innerPrefix = " ";
pw.println(TAG + "states: ");
pw.print(innerPrefix + "sTimeout=");
- pw.println(sTimeout);
+ pw.println(mTimeout);
pw.print(innerPrefix + "sListeners=");
- pw.println(sListeners);
+ pw.println(mListeners);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java
index 721382d..60709be 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java
@@ -30,6 +30,8 @@
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
+import com.android.wm.shell.common.ShellExecutor;
+
import java.io.PrintWriter;
/**
@@ -41,7 +43,8 @@
private static final String TAG = "OneHandedTouchHandler";
private final Rect mLastUpdatedBounds = new Rect();
- private OneHandedTimeoutHandler mTimeoutHandler;
+ private final OneHandedTimeoutHandler mTimeoutHandler;
+ private final ShellExecutor mMainExecutor;
@VisibleForTesting
InputMonitor mInputMonitor;
@@ -54,8 +57,10 @@
private boolean mIsOnStopTransitioning;
private boolean mIsInOutsideRegion;
- public OneHandedTouchHandler() {
- mTimeoutHandler = OneHandedTimeoutHandler.get();
+ public OneHandedTouchHandler(OneHandedTimeoutHandler timeoutHandler,
+ ShellExecutor mainExecutor) {
+ mTimeoutHandler = timeoutHandler;
+ mMainExecutor = mainExecutor;
updateIsEnabled();
}
@@ -128,7 +133,7 @@
mInputMonitor = InputManager.getInstance().monitorGestureInput(
"onehanded-touch", DEFAULT_DISPLAY);
mInputEventReceiver = new EventReceiver(
- mInputMonitor.getInputChannel(), Looper.getMainLooper());
+ mInputMonitor.getInputChannel(), mMainExecutor.getLooper());
}
}
@@ -150,6 +155,7 @@
pw.println(mLastUpdatedBounds);
}
+ // TODO: Use BatchedInputEventReceiver
private class EventReceiver extends InputEventReceiver {
EventReceiver(InputChannel channel, Looper looper) {
super(channel, looper);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
index a944e3b..492130b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
@@ -21,7 +21,6 @@
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
-import android.os.Handler;
import android.os.SystemProperties;
import android.provider.Settings;
import android.view.Gravity;
@@ -36,6 +35,7 @@
import androidx.annotation.NonNull;
import com.android.wm.shell.R;
+import com.android.wm.shell.common.ShellExecutor;
import java.io.PrintWriter;
@@ -57,7 +57,6 @@
private View mTutorialView;
private Point mDisplaySize = new Point();
- private Handler mUpdateHandler;
private ContentResolver mContentResolver;
private boolean mCanShowTutorial;
private String mStartOneHandedDescription;
@@ -82,69 +81,67 @@
private final OneHandedAnimationCallback mAnimationCallback = new OneHandedAnimationCallback() {
@Override
public void onTutorialAnimationUpdate(int offset) {
- mUpdateHandler.post(() -> onAnimationUpdate(offset));
+ onAnimationUpdate(offset);
}
@Override
public void onOneHandedAnimationStart(
OneHandedAnimationController.OneHandedTransitionAnimator animator) {
- mUpdateHandler.post(() -> {
- final Rect startValue = (Rect) animator.getStartValue();
- if (mTriggerState == ONE_HANDED_TRIGGER_STATE.UNSET) {
- mTriggerState = (startValue.top == 0)
- ? ONE_HANDED_TRIGGER_STATE.ENTERING : ONE_HANDED_TRIGGER_STATE.EXITING;
- if (mCanShowTutorial && mTriggerState == ONE_HANDED_TRIGGER_STATE.ENTERING) {
- createTutorialTarget();
- }
+ final Rect startValue = (Rect) animator.getStartValue();
+ if (mTriggerState == ONE_HANDED_TRIGGER_STATE.UNSET) {
+ mTriggerState = (startValue.top == 0)
+ ? ONE_HANDED_TRIGGER_STATE.ENTERING : ONE_HANDED_TRIGGER_STATE.EXITING;
+ if (mCanShowTutorial && mTriggerState == ONE_HANDED_TRIGGER_STATE.ENTERING) {
+ createTutorialTarget();
}
- });
+ }
}
};
- public OneHandedTutorialHandler(Context context) {
+ public OneHandedTutorialHandler(Context context, ShellExecutor mainExecutor) {
context.getDisplay().getRealSize(mDisplaySize);
mPackageName = context.getPackageName();
mContentResolver = context.getContentResolver();
- mUpdateHandler = new Handler();
mWindowManager = context.getSystemService(WindowManager.class);
mAccessibilityManager = (AccessibilityManager)
context.getSystemService(Context.ACCESSIBILITY_SERVICE);
- mTargetViewContainer = new FrameLayout(context);
- mTargetViewContainer.setClipChildren(false);
+
+ mStartOneHandedDescription = context.getResources().getString(
+ R.string.accessibility_action_start_one_handed);
+ mStopOneHandedDescription = context.getResources().getString(
+ R.string.accessibility_action_stop_one_handed);
+ mCanShowTutorial = (Settings.Secure.getInt(mContentResolver,
+ Settings.Secure.ONE_HANDED_TUTORIAL_SHOW_COUNT, 0) >= MAX_TUTORIAL_SHOW_COUNT)
+ ? false : true;
final float offsetPercentageConfig = context.getResources().getFraction(
R.fraction.config_one_handed_offset, 1, 1);
final int sysPropPercentageConfig = SystemProperties.getInt(
ONE_HANDED_MODE_OFFSET_PERCENTAGE, Math.round(offsetPercentageConfig * 100.0f));
mTutorialAreaHeight = Math.round(mDisplaySize.y * (sysPropPercentageConfig / 100.0f));
- mTutorialView = LayoutInflater.from(context).inflate(R.layout.one_handed_tutorial, null);
- mTargetViewContainer.addView(mTutorialView);
- mCanShowTutorial = (Settings.Secure.getInt(mContentResolver,
- Settings.Secure.ONE_HANDED_TUTORIAL_SHOW_COUNT, 0) >= MAX_TUTORIAL_SHOW_COUNT)
- ? false : true;
- mStartOneHandedDescription = context.getResources().getString(
- R.string.accessibility_action_start_one_handed);
- mStopOneHandedDescription = context.getResources().getString(
- R.string.accessibility_action_stop_one_handed);
+
+ mainExecutor.execute(() -> {
+ mTutorialView = LayoutInflater.from(context).inflate(R.layout.one_handed_tutorial,
+ null);
+ mTargetViewContainer = new FrameLayout(context);
+ mTargetViewContainer.setClipChildren(false);
+ mTargetViewContainer.addView(mTutorialView);
+ });
}
@Override
public void onStartFinished(Rect bounds) {
- mUpdateHandler.post(() -> {
- updateFinished(View.VISIBLE, 0f);
- updateTutorialCount();
- announcementForScreenReader(true);
- mTriggerState = ONE_HANDED_TRIGGER_STATE.UNSET;
- });
+ updateFinished(View.VISIBLE, 0f);
+ updateTutorialCount();
+ announcementForScreenReader(true);
+ mTriggerState = ONE_HANDED_TRIGGER_STATE.UNSET;
}
@Override
public void onStopFinished(Rect bounds) {
- mUpdateHandler.post(() -> {
- updateFinished(View.INVISIBLE, -mTargetViewContainer.getHeight());
- announcementForScreenReader(false);
- removeTutorialFromWindowManager();
- mTriggerState = ONE_HANDED_TRIGGER_STATE.UNSET;
- });
+ updateFinished(View.INVISIBLE, -mTargetViewContainer.getHeight());
+ announcementForScreenReader(false);
+ removeTutorialFromWindowManager();
+ mTriggerState = ONE_HANDED_TRIGGER_STATE.UNSET;
}
private void updateFinished(int visible, float finalPosition) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
index 7194fc7..3b65899 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
@@ -53,7 +53,7 @@
private List<AccessibilityNodeInfo> mAccessibilityNodeInfoList;
private final Context mContext;
- private final ShellExecutor mShellMainExcutor;
+ private final ShellExecutor mMainExcutor;
private final @NonNull PipBoundsState mPipBoundsState;
private final PipMotionHelper mMotionHelper;
private final PipTaskOrganizer mTaskOrganizer;
@@ -72,9 +72,9 @@
@NonNull PipBoundsState pipBoundsState, PipMotionHelper motionHelper,
PipTaskOrganizer taskOrganizer, PipSnapAlgorithm snapAlgorithm,
AccessibilityCallbacks callbacks, Runnable updateMovementBoundCallback,
- ShellExecutor shellMainExcutor) {
+ ShellExecutor mainExcutor) {
mContext = context;
- mShellMainExcutor = shellMainExcutor;
+ mMainExcutor = mainExcutor;
mPipBoundsState = pipBoundsState;
mMotionHelper = motionHelper;
mTaskOrganizer = taskOrganizer;
@@ -271,7 +271,7 @@
IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid, MagnificationSpec spec,
Bundle arguments) throws RemoteException {
- mShellMainExcutor.execute(() -> {
+ mMainExcutor.execute(() -> {
PipAccessibilityInteractionConnection.this
.findAccessibilityNodeInfoByAccessibilityId(accessibilityNodeId, bounds,
interactionId, callback, flags, interrogatingPid, interrogatingTid,
@@ -285,7 +285,7 @@
IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid, MagnificationSpec spec)
throws RemoteException {
- mShellMainExcutor.execute(() -> {
+ mMainExcutor.execute(() -> {
PipAccessibilityInteractionConnection.this.findAccessibilityNodeInfosByViewId(
accessibilityNodeId, viewId, bounds, interactionId, callback, flags,
interrogatingPid, interrogatingTid, spec);
@@ -298,7 +298,7 @@
IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid, MagnificationSpec spec)
throws RemoteException {
- mShellMainExcutor.execute(() -> {
+ mMainExcutor.execute(() -> {
PipAccessibilityInteractionConnection.this.findAccessibilityNodeInfosByText(
accessibilityNodeId, text, bounds, interactionId, callback, flags,
interrogatingPid, interrogatingTid, spec);
@@ -310,7 +310,7 @@
int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid, MagnificationSpec spec)
throws RemoteException {
- mShellMainExcutor.execute(() -> {
+ mMainExcutor.execute(() -> {
PipAccessibilityInteractionConnection.this.findFocus(accessibilityNodeId, focusType,
bounds, interactionId, callback, flags, interrogatingPid, interrogatingTid,
spec);
@@ -322,7 +322,7 @@
int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid, MagnificationSpec spec)
throws RemoteException {
- mShellMainExcutor.execute(() -> {
+ mMainExcutor.execute(() -> {
PipAccessibilityInteractionConnection.this.focusSearch(accessibilityNodeId,
direction,
bounds, interactionId, callback, flags, interrogatingPid, interrogatingTid,
@@ -335,7 +335,7 @@
Bundle arguments, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid) throws RemoteException {
- mShellMainExcutor.execute(() -> {
+ mMainExcutor.execute(() -> {
PipAccessibilityInteractionConnection.this.performAccessibilityAction(
accessibilityNodeId, action, arguments, interactionId, callback, flags,
interrogatingPid, interrogatingTid);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipController.java
deleted file mode 100644
index ffa6c99..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipController.java
+++ /dev/null
@@ -1,550 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.pip.tv;
-
-import static android.app.ActivityTaskManager.INVALID_STACK_ID;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.content.Intent.ACTION_MEDIA_RESOURCE_GRANTED;
-
-import static com.android.wm.shell.pip.tv.PipNotification.ACTION_CLOSE;
-import static com.android.wm.shell.pip.tv.PipNotification.ACTION_MENU;
-
-import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
-import android.app.ActivityTaskManager.RootTaskInfo;
-import android.app.IActivityTaskManager;
-import android.app.RemoteAction;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ParceledListSlice;
-import android.content.res.Configuration;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.DisplayInfo;
-
-import com.android.wm.shell.R;
-import com.android.wm.shell.WindowManagerShellWrapper;
-import com.android.wm.shell.common.TaskStackListenerCallback;
-import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.pip.PinnedStackListenerForwarder;
-import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipMediaController;
-import com.android.wm.shell.pip.PipTaskOrganizer;
-
-import java.util.Objects;
-
-/**
- * Manages the picture-in-picture (PIP) UI and states.
- */
-public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallback,
- TvPipMenuController.Delegate {
- private static final String TAG = "TvPipController";
- static final boolean DEBUG = false;
-
- /**
- * Unknown or invalid state
- */
- public static final int STATE_UNKNOWN = -1;
- /**
- * State when there's no PIP.
- */
- public static final int STATE_NO_PIP = 0;
- /**
- * State when PIP is shown. This is used as default PIP state.
- */
- public static final int STATE_PIP = 1;
- /**
- * State when PIP menu dialog is shown.
- */
- public static final int STATE_PIP_MENU = 2;
-
- private static final int TASK_ID_NO_PIP = -1;
- private static final int INVALID_RESOURCE_TYPE = -1;
-
- private final Context mContext;
- private final PipBoundsState mPipBoundsState;
- private final PipBoundsAlgorithm mPipBoundsAlgorithm;
- private final PipTaskOrganizer mPipTaskOrganizer;
- private final PipMediaController mPipMediaController;
- private final TvPipMenuController mTvPipMenuController;
- private final PipNotification mPipNotification;
-
- private IActivityTaskManager mActivityTaskManager;
- private int mState = STATE_NO_PIP;
- private final Handler mHandler = new Handler();
- private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
- private int mPipTaskId = TASK_ID_NO_PIP;
- private int mPinnedStackId = INVALID_STACK_ID;
- private String[] mLastPackagesResourceGranted;
- private ParceledListSlice<RemoteAction> mCustomActions;
- private WindowManagerShellWrapper mWindowManagerShellWrapper;
- private int mResizeAnimationDuration;
-
- // Used to calculate the movement bounds
- private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
- private final Rect mTmpInsetBounds = new Rect();
-
- // Keeps track of the IME visibility to adjust the PiP when the IME is visible
- private boolean mImeVisible;
- private int mImeHeightAdjustment;
-
- private final Runnable mClosePipRunnable = this::closePip;
- private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (DEBUG) {
- Log.d(TAG, "mBroadcastReceiver, action: " + intent.getAction());
- }
- switch (intent.getAction()) {
- case ACTION_MENU:
- showPictureInPictureMenu();
- break;
- case ACTION_CLOSE:
- closePip();
- break;
- case ACTION_MEDIA_RESOURCE_GRANTED:
- String[] packageNames = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
- int resourceType = intent.getIntExtra(Intent.EXTRA_MEDIA_RESOURCE_TYPE,
- INVALID_RESOURCE_TYPE);
- if (packageNames != null && packageNames.length > 0
- && resourceType == Intent.EXTRA_MEDIA_RESOURCE_TYPE_VIDEO_CODEC) {
- handleMediaResourceGranted(packageNames);
- }
- break;
- }
- }
- };
-
- private final PinnedStackListenerForwarder.PinnedStackListener mPinnedStackListener =
- new PipControllerPinnedStackListener();
-
- @Override
- public void registerSessionListenerForCurrentUser() {
- mPipMediaController.registerSessionListenerForCurrentUser();
- }
-
- /**
- * Handler for messages from the PIP controller.
- */
- private class PipControllerPinnedStackListener extends
- PinnedStackListenerForwarder.PinnedStackListener {
- @Override
- public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
- mPipBoundsState.setImeVisibility(imeVisible, imeHeight);
- if (mState == STATE_PIP) {
- if (mImeVisible != imeVisible) {
- if (imeVisible) {
- // Save the IME height adjustment, and offset to not occlude the IME
- mPipBoundsState.getNormalBounds().offset(0, -imeHeight);
- mImeHeightAdjustment = imeHeight;
- } else {
- // Apply the inverse adjustment when the IME is hidden
- mPipBoundsState.getNormalBounds().offset(0, mImeHeightAdjustment);
- }
- mImeVisible = imeVisible;
- resizePinnedStack(STATE_PIP);
- }
- }
- }
-
- @Override
- public void onMovementBoundsChanged(boolean fromImeAdjustment) {
- mTmpDisplayInfo.copyFrom(mPipBoundsState.getDisplayInfo());
- mPipBoundsAlgorithm.getInsetBounds(mTmpInsetBounds);
- }
-
- @Override
- public void onActionsChanged(ParceledListSlice<RemoteAction> actions) {
- mCustomActions = actions;
- mTvPipMenuController.setAppActions(mCustomActions);
- }
- }
-
- public PipController(Context context,
- PipBoundsState pipBoundsState,
- PipBoundsAlgorithm pipBoundsAlgorithm,
- PipTaskOrganizer pipTaskOrganizer,
- TvPipMenuController tvPipMenuController,
- PipMediaController pipMediaController,
- PipNotification pipNotification,
- TaskStackListenerImpl taskStackListener,
- WindowManagerShellWrapper windowManagerShellWrapper) {
- mContext = context;
- mPipBoundsState = pipBoundsState;
- mPipNotification = pipNotification;
- mPipBoundsAlgorithm = pipBoundsAlgorithm;
- mPipMediaController = pipMediaController;
- mTvPipMenuController = tvPipMenuController;
- mTvPipMenuController.setDelegate(this);
- // Ensure that we have the display info in case we get calls to update the bounds
- // before the listener calls back
- final DisplayInfo displayInfo = new DisplayInfo();
- context.getDisplay().getDisplayInfo(displayInfo);
- mPipBoundsState.setDisplayInfo(displayInfo);
-
- mResizeAnimationDuration = context.getResources()
- .getInteger(R.integer.config_pipResizeAnimationDuration);
- mPipTaskOrganizer = pipTaskOrganizer;
- mPipTaskOrganizer.registerPipTransitionCallback(this);
- mActivityTaskManager = ActivityTaskManager.getService();
-
- final IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(ACTION_CLOSE);
- intentFilter.addAction(ACTION_MENU);
- intentFilter.addAction(ACTION_MEDIA_RESOURCE_GRANTED);
- mContext.registerReceiver(mBroadcastReceiver, intentFilter, UserHandle.USER_ALL);
-
- // Initialize the last orientation and apply the current configuration
- Configuration initialConfig = mContext.getResources().getConfiguration();
- mLastOrientation = initialConfig.orientation;
- loadConfigurationsAndApply(initialConfig);
-
- mWindowManagerShellWrapper = windowManagerShellWrapper;
- try {
- mWindowManagerShellWrapper.addPinnedStackListener(mPinnedStackListener);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to register pinned stack listener", e);
- }
-
- // Handle for system task stack changes.
- taskStackListener.addListener(
- new TaskStackListenerCallback() {
- @Override
- public void onTaskStackChanged() {
- PipController.this.onTaskStackChanged();
- }
-
- @Override
- public void onActivityPinned(String packageName, int userId, int taskId,
- int stackId) {
- PipController.this.onActivityPinned(packageName);
- }
-
- @Override
- public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
- boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
- PipController.this.onActivityRestartAttempt(task, clearedTask);
- }
- });
- }
-
- private void loadConfigurationsAndApply(Configuration newConfig) {
- if (mLastOrientation != newConfig.orientation) {
- // Don't resize the pinned stack on orientation change. TV does not care about this case
- // and this could clobber the existing animation to the new bounds calculated by WM.
- mLastOrientation = newConfig.orientation;
- return;
- }
-
- final Rect menuBounds = Rect.unflattenFromString(
- mContext.getResources().getString(R.string.pip_menu_bounds));
- mPipBoundsState.setExpandedBounds(menuBounds);
-
- resizePinnedStack(getPinnedTaskInfo() == null ? STATE_NO_PIP : STATE_PIP);
- }
-
- /**
- * Updates the PIP per configuration changed.
- */
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- loadConfigurationsAndApply(newConfig);
- mPipNotification.onConfigurationChanged(mContext);
- }
-
- /**
- * Shows the picture-in-picture menu if an activity is in picture-in-picture mode.
- */
- public void showPictureInPictureMenu() {
- if (DEBUG) Log.d(TAG, "showPictureInPictureMenu(), current state=" + getStateDescription());
-
- if (getState() == STATE_PIP) {
- resizePinnedStack(STATE_PIP_MENU);
- }
- }
-
- /**
- * Closes PIP (PIPed activity and PIP system UI).
- */
- @Override
- public void closePip() {
- if (DEBUG) Log.d(TAG, "closePip(), current state=" + getStateDescription());
-
- closePipInternal(true);
- }
-
- private void closePipInternal(boolean removePipStack) {
- if (DEBUG) {
- Log.d(TAG,
- "closePipInternal() removePipStack=" + removePipStack + ", current state="
- + getStateDescription());
- }
-
- mState = STATE_NO_PIP;
- mPipTaskId = TASK_ID_NO_PIP;
- if (removePipStack) {
- try {
- mActivityTaskManager.removeTask(mPinnedStackId);
- } catch (RemoteException e) {
- Log.e(TAG, "removeTask failed", e);
- } finally {
- mPinnedStackId = INVALID_STACK_ID;
- }
- }
- mPipNotification.dismiss();
- mTvPipMenuController.hideMenu();
- mHandler.removeCallbacks(mClosePipRunnable);
- }
-
- @Override
- public void movePipToNormalPosition() {
- resizePinnedStack(PipController.STATE_PIP);
- }
-
- /**
- * Moves the PIPed activity to the fullscreen and closes PIP system UI.
- */
- @Override
- public void movePipToFullscreen() {
- if (DEBUG) Log.d(TAG, "movePipToFullscreen(), current state=" + getStateDescription());
-
- mPipTaskId = TASK_ID_NO_PIP;
- mTvPipMenuController.hideMenu();
- mPipNotification.dismiss();
-
- resizePinnedStack(STATE_NO_PIP);
- }
-
- private void onActivityPinned(String packageName) {
- final RootTaskInfo taskInfo = getPinnedTaskInfo();
- if (DEBUG) Log.d(TAG, "onActivityPinned, task=" + taskInfo);
- if (taskInfo == null) {
- Log.w(TAG, "Cannot find pinned stack");
- return;
- }
-
- // At this point PipBoundsState knows the correct aspect ratio for this pinned task, so we
- // use PipBoundsAlgorithm to calculate the normal bounds for the task (PipBoundsAlgorithm
- // will query PipBoundsState for the aspect ratio) and pass the bounds over to the
- // PipBoundsState.
- mPipBoundsState.setNormalBounds(mPipBoundsAlgorithm.getNormalBounds());
-
- mPinnedStackId = taskInfo.taskId;
- mPipTaskId = taskInfo.childTaskIds[taskInfo.childTaskIds.length - 1];
-
- // Set state to STATE_PIP so we show it when the pinned stack animation ends.
- mState = STATE_PIP;
- mPipMediaController.onActivityPinned();
- mPipNotification.show(packageName);
- }
-
- private void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
- boolean clearedTask) {
- if (task.getWindowingMode() != WINDOWING_MODE_PINNED) {
- return;
- }
- if (DEBUG) Log.d(TAG, "onPinnedActivityRestartAttempt()");
-
- // If PIPed activity is launched again by Launcher or intent, make it fullscreen.
- movePipToFullscreen();
- }
-
- private void onTaskStackChanged() {
- if (DEBUG) Log.d(TAG, "onTaskStackChanged()");
-
- if (getState() != STATE_NO_PIP) {
- boolean hasPip = false;
-
- RootTaskInfo taskInfo = getPinnedTaskInfo();
- if (taskInfo == null || taskInfo.childTaskIds == null) {
- Log.w(TAG, "There is nothing in pinned stack");
- closePipInternal(false);
- return;
- }
- for (int i = taskInfo.childTaskIds.length - 1; i >= 0; --i) {
- if (taskInfo.childTaskIds[i] == mPipTaskId) {
- // PIP task is still alive.
- hasPip = true;
- break;
- }
- }
- if (!hasPip) {
- // PIP task doesn't exist anymore in PINNED_STACK.
- closePipInternal(true);
- return;
- }
- }
- if (getState() == STATE_PIP) {
- if (!Objects.equals(mPipBoundsState.getBounds(), mPipBoundsState.getNormalBounds())) {
- resizePinnedStack(STATE_PIP);
- }
- }
- }
-
- /**
- * Resize the Pip to the appropriate size for the input state.
- *
- * @param state In Pip state also used to determine the new size for the Pip.
- */
- public void resizePinnedStack(int state) {
- if (DEBUG) {
- Log.d(TAG, "resizePinnedStack() state=" + stateToName(state) + ", current state="
- + getStateDescription(), new Exception());
- }
- final boolean wasStateNoPip = (mState == STATE_NO_PIP);
- mTvPipMenuController.hideMenu();
- mState = state;
- final Rect newBounds;
- switch (mState) {
- case STATE_NO_PIP:
- newBounds = null;
- // If the state was already STATE_NO_PIP, then do not resize the stack below as it
- // will not exist
- if (wasStateNoPip) {
- return;
- }
- break;
- case STATE_PIP_MENU:
- newBounds = mPipBoundsState.getExpandedBounds();
- break;
- case STATE_PIP: // fallthrough
- default:
- newBounds = mPipBoundsState.getNormalBounds();
- break;
- }
- if (newBounds != null) {
- mPipTaskOrganizer.scheduleAnimateResizePip(newBounds, mResizeAnimationDuration, null);
- } else {
- mPipTaskOrganizer.exitPip(mResizeAnimationDuration);
- }
- }
-
- /**
- * @return the current state.
- */
- private int getState() {
- return mState;
- }
-
- private void showPipMenu() {
- if (DEBUG) Log.d(TAG, "showPipMenu(), current state=" + getStateDescription());
-
- mState = STATE_PIP_MENU;
- mTvPipMenuController.showMenu();
- }
-
- /**
- * Returns {@code true} if PIP is shown.
- */
- public boolean isPipShown() {
- return mState != STATE_NO_PIP;
- }
-
- private RootTaskInfo getPinnedTaskInfo() {
- RootTaskInfo taskInfo = null;
- try {
- taskInfo = ActivityTaskManager.getService().getRootTaskInfo(
- WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
- } catch (RemoteException e) {
- Log.e(TAG, "getRootTaskInfo failed", e);
- }
- if (DEBUG) Log.d(TAG, "getPinnedTaskInfo(), taskInfo=" + taskInfo);
- return taskInfo;
- }
-
- private void handleMediaResourceGranted(String[] packageNames) {
- if (getState() == STATE_NO_PIP) {
- mLastPackagesResourceGranted = packageNames;
- } else {
- boolean requestedFromLastPackages = false;
- if (mLastPackagesResourceGranted != null) {
- for (String packageName : mLastPackagesResourceGranted) {
- for (String newPackageName : packageNames) {
- if (TextUtils.equals(newPackageName, packageName)) {
- requestedFromLastPackages = true;
- break;
- }
- }
- }
- }
- mLastPackagesResourceGranted = packageNames;
- if (!requestedFromLastPackages) {
- closePip();
- }
- }
- }
-
- @Override
- public void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) {
- }
-
- PipMediaController getPipMediaController() {
- return mPipMediaController;
- }
-
- @Override
- public void onPipTransitionStarted(ComponentName activity, int direction, Rect pipBounds) {
- }
-
- @Override
- public void onPipTransitionFinished(ComponentName activity, int direction) {
- onPipTransitionFinishedOrCanceled();
- }
-
- @Override
- public void onPipTransitionCanceled(ComponentName activity, int direction) {
- onPipTransitionFinishedOrCanceled();
- }
-
- private void onPipTransitionFinishedOrCanceled() {
- if (DEBUG) Log.d(TAG, "onPipTransitionFinishedOrCanceled()");
-
- if (getState() == STATE_PIP_MENU) {
- showPipMenu();
- }
- }
-
- private String getStateDescription() {
- return stateToName(mState);
- }
-
- private static String stateToName(int state) {
- switch (state) {
- case STATE_NO_PIP:
- return "NO_PIP";
-
- case STATE_PIP:
- return "PIP";
-
- case STATE_PIP_MENU:
- return "PIP_MENU";
-
- default:
- return "UNKNOWN(" + state + ")";
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
new file mode 100644
index 0000000..8bc60f9
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -0,0 +1,421 @@
+/*
+ * 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.wm.shell.pip.tv;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+
+import android.annotation.IntDef;
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.RemoteAction;
+import android.app.TaskInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.DisplayInfo;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.WindowManagerShellWrapper;
+import com.android.wm.shell.common.TaskStackListenerCallback;
+import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.pip.PinnedStackListenerForwarder;
+import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipMediaController;
+import com.android.wm.shell.pip.PipTaskOrganizer;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Manages the picture-in-picture (PIP) UI and states.
+ */
+public class TvPipController implements Pip, PipTaskOrganizer.PipTransitionCallback,
+ TvPipMenuController.Delegate, TvPipNotificationController.Delegate {
+ private static final String TAG = "TvPipController";
+ static final boolean DEBUG = true;
+
+ private static final int NONEXISTENT_TASK_ID = -1;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "STATE_" }, value = {
+ STATE_NO_PIP,
+ STATE_PIP,
+ STATE_PIP_MENU
+ })
+ public @interface State {}
+
+ /**
+ * State when there is no applications in Pip.
+ */
+ private static final int STATE_NO_PIP = 0;
+ /**
+ * State when there is an applications in Pip and the Pip window located at its "normal" place
+ * (usually the bottom right corner).
+ */
+ private static final int STATE_PIP = 1;
+ /**
+ * State when there is an applications in Pip and the Pip menu is open. In this state Pip window
+ * is usually moved from its "normal" position on the screen to the "menu" position - which is
+ * often at the middle of the screen, and gets slightly scaled up.
+ */
+ private static final int STATE_PIP_MENU = 2;
+
+ private final Context mContext;
+
+ private final PipBoundsState mPipBoundsState;
+ private final PipBoundsAlgorithm mPipBoundsAlgorithm;
+ private final PipTaskOrganizer mPipTaskOrganizer;
+ private final PipMediaController mPipMediaController;
+ private final TvPipNotificationController mPipNotificationController;
+ private final TvPipMenuController mTvPipMenuController;
+
+ private @State int mState = STATE_NO_PIP;
+ private int mPinnedTaskId = NONEXISTENT_TASK_ID;
+
+ private int mResizeAnimationDuration;
+
+ public TvPipController(
+ Context context,
+ PipBoundsState pipBoundsState,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipTaskOrganizer pipTaskOrganizer,
+ TvPipMenuController tvPipMenuController,
+ PipMediaController pipMediaController,
+ TvPipNotificationController pipNotificationController,
+ TaskStackListenerImpl taskStackListener,
+ WindowManagerShellWrapper wmShell) {
+ mContext = context;
+
+ mPipBoundsState = pipBoundsState;
+ mPipBoundsState.setDisplayInfo(getDisplayInfo());
+ mPipBoundsAlgorithm = pipBoundsAlgorithm;
+
+ mPipMediaController = pipMediaController;
+
+ mPipNotificationController = pipNotificationController;
+ mPipNotificationController.setDelegate(this);
+
+ mTvPipMenuController = tvPipMenuController;
+ mTvPipMenuController.setDelegate(this);
+
+ mPipTaskOrganizer = pipTaskOrganizer;
+ mPipTaskOrganizer.registerPipTransitionCallback(this);
+
+ loadConfigurations();
+
+ registerTaskStackListenerCallback(taskStackListener);
+ registerWmShellPinnedStackListener(wmShell);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ if (DEBUG) Log.d(TAG, "onConfigurationChanged(), state=" + stateToName(mState));
+
+ if (isPipShown()) {
+ if (DEBUG) Log.d(TAG, " > closing Pip.");
+ closePip();
+ }
+
+ loadConfigurations();
+ mPipNotificationController.onConfigurationChanged(mContext);
+ }
+
+ /**
+ * Returns {@code true} if Pip is shown.
+ */
+ @Override
+ public boolean isPipShown() {
+ return mState != STATE_NO_PIP;
+ }
+
+ /**
+ * Starts the process if bringing up the Pip menu if by issuing a command to move Pip
+ * task/window to the "Menu" position. We'll show the actual Menu UI (eg. actions) once the Pip
+ * task/window is properly positioned in {@link #onPipTransitionFinished(ComponentName, int)}.
+ */
+ @Override
+ public void showPictureInPictureMenu() {
+ if (DEBUG) Log.d(TAG, "showPictureInPictureMenu(), state=" + stateToName(mState));
+
+ if (mState != STATE_PIP) {
+ if (DEBUG) Log.d(TAG, " > cannot open Menu from the current state.");
+ return;
+ }
+
+ setState(STATE_PIP_MENU);
+ resizePinnedStack(STATE_PIP_MENU);
+ }
+
+ /**
+ * Moves Pip window to its "normal" position.
+ */
+ @Override
+ public void movePipToNormalPosition() {
+ if (DEBUG) Log.d(TAG, "movePipToNormalPosition(), state=" + stateToName(mState));
+
+ setState(STATE_PIP);
+ resizePinnedStack(STATE_PIP);
+ }
+
+ /**
+ * Opens the "Pip-ed" Activity fullscreen.
+ */
+ @Override
+ public void movePipToFullscreen() {
+ if (DEBUG) Log.d(TAG, "movePipToFullscreen(), state=" + stateToName(mState));
+
+ mPipTaskOrganizer.exitPip(mResizeAnimationDuration);
+ onPipDisappeared();
+ }
+
+ /**
+ * Closes Pip window.
+ */
+ @Override
+ public void closePip() {
+ if (DEBUG) Log.d(TAG, "closePip(), state=" + stateToName(mState));
+
+ removeTask(mPinnedTaskId);
+ onPipDisappeared();
+ }
+
+ /**
+ * Resizes the Pip task/window to the appropriate size for the given state.
+ * This is a legacy API. Now we expect that the state argument passed to it should always match
+ * the current state of the Controller. If it does not match an {@link IllegalArgumentException}
+ * will be thrown. However, if the passed state does match - we'll determine the right bounds
+ * to the state and will move Pip task/window there.
+ *
+ * @param state the to determine the Pip bounds. IMPORTANT: should always match the current
+ * state of the Controller.
+ */
+ @Override
+ public void resizePinnedStack(@State int state) {
+ if (state != mState) {
+ throw new IllegalArgumentException("The passed state should match the current state!");
+ }
+ if (DEBUG) Log.d(TAG, "resizePinnedStack() state=" + stateToName(mState));
+
+ final Rect newBounds;
+ switch (mState) {
+ case STATE_PIP_MENU:
+ newBounds = mPipBoundsState.getExpandedBounds();
+ break;
+
+ case STATE_PIP:
+ // Let PipBoundsAlgorithm figure out what the correct bounds are at the moment.
+ // Internally, it will get the "default" bounds from PipBoundsState and adjust them
+ // as needed to account for things like IME state (will query PipBoundsState for
+ // this information as well, so it's important to keep PipBoundsState up to date).
+ newBounds = mPipBoundsAlgorithm.getNormalBounds();
+ break;
+
+ case STATE_NO_PIP:
+ default:
+ return;
+ }
+
+ mPipTaskOrganizer.scheduleAnimateResizePip(newBounds, mResizeAnimationDuration, null);
+ }
+
+ @Override
+ public void registerSessionListenerForCurrentUser() {
+ mPipMediaController.registerSessionListenerForCurrentUser();
+ }
+
+ private void checkIfPinnedTaskAppeared() {
+ final TaskInfo pinnedTask = getPinnedTaskInfo();
+ if (DEBUG) Log.d(TAG, "checkIfPinnedTaskAppeared(), task=" + pinnedTask);
+ if (pinnedTask == null) return;
+ mPinnedTaskId = pinnedTask.taskId;
+ setState(STATE_PIP);
+
+ mPipMediaController.onActivityPinned();
+ mPipNotificationController.show(pinnedTask.topActivity.getPackageName());
+ }
+
+ private void checkIfPinnedTaskIsGone() {
+ if (DEBUG) Log.d(TAG, "onTaskStackChanged()");
+
+ if (isPipShown() && getPinnedTaskInfo() == null) {
+ Log.w(TAG, "Pinned task is gone.");
+ onPipDisappeared();
+ }
+ }
+
+ private void onPipDisappeared() {
+ if (DEBUG) Log.d(TAG, "onPipDisappeared() state=" + stateToName(mState));
+
+ mPipNotificationController.dismiss();
+ mTvPipMenuController.hideMenu();
+ setState(STATE_NO_PIP);
+ mPinnedTaskId = NONEXISTENT_TASK_ID;
+ }
+
+ @Override
+ public void onPipTransitionStarted(ComponentName activity, int direction, Rect pipBounds) {
+ if (DEBUG) Log.d(TAG, "onPipTransition_Started(), state=" + stateToName(mState));
+ }
+
+ @Override
+ public void onPipTransitionCanceled(ComponentName activity, int direction) {
+ if (DEBUG) Log.d(TAG, "onPipTransition_Canceled(), state=" + stateToName(mState));
+ }
+
+ @Override
+ public void onPipTransitionFinished(ComponentName activity, int direction) {
+ if (DEBUG) Log.d(TAG, "onPipTransition_Finished(), state=" + stateToName(mState));
+
+ if (mState == STATE_PIP_MENU) {
+ if (DEBUG) Log.d(TAG, " > show menu");
+ mTvPipMenuController.showMenu();
+ }
+ }
+
+ private void setState(@State int state) {
+ if (DEBUG) {
+ Log.d(TAG, "setState(), state=" + stateToName(state) + ", prev="
+ + stateToName(mState));
+ }
+ mState = state;
+ }
+
+ private void loadConfigurations() {
+ final Resources res = mContext.getResources();
+ mResizeAnimationDuration = res.getInteger(R.integer.config_pipResizeAnimationDuration);
+ // "Cache" bounds for the Pip menu as "expanded" bounds in PipBoundsState. We'll refer back
+ // to this value in resizePinnedStack(), when we are adjusting Pip task/window position for
+ // the menu.
+ mPipBoundsState.setExpandedBounds(
+ Rect.unflattenFromString(res.getString(R.string.pip_menu_bounds)));
+ }
+
+ private DisplayInfo getDisplayInfo() {
+ final DisplayInfo displayInfo = new DisplayInfo();
+ mContext.getDisplay().getDisplayInfo(displayInfo);
+ return displayInfo;
+ }
+
+ private void registerTaskStackListenerCallback(TaskStackListenerImpl taskStackListener) {
+ taskStackListener.addListener(new TaskStackListenerCallback() {
+ @Override
+ public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
+ checkIfPinnedTaskAppeared();
+ }
+
+ @Override
+ public void onTaskStackChanged() {
+ checkIfPinnedTaskIsGone();
+ }
+
+ @Override
+ public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
+ boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
+ if (task.getWindowingMode() == WINDOWING_MODE_PINNED) {
+ if (DEBUG) Log.d(TAG, "onPinnedActivityRestartAttempt()");
+
+ // If the "Pip-ed" Activity is launched again by Launcher or intent, make it
+ // fullscreen.
+ movePipToFullscreen();
+ }
+ }
+ });
+ }
+
+ private void registerWmShellPinnedStackListener(WindowManagerShellWrapper wmShell) {
+ try {
+ wmShell.addPinnedStackListener(new PinnedStackListenerForwarder.PinnedStackListener() {
+ @Override
+ public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
+ if (DEBUG) {
+ Log.d(TAG, "onImeVisibilityChanged(), visible=" + imeVisible
+ + ", height=" + imeHeight);
+ }
+
+ if (imeVisible == mPipBoundsState.isImeShowing()
+ && (!imeVisible || imeHeight == mPipBoundsState.getImeHeight())) {
+ // Nothing changed: either IME has been and remains invisible, or remains
+ // visible with the same height.
+ return;
+ }
+ mPipBoundsState.setImeVisibility(imeVisible, imeHeight);
+ // "Normal" Pip bounds may have changed, so if we are in the "normal" state,
+ // let's update the bounds.
+ if (mState == STATE_PIP) {
+ resizePinnedStack(STATE_PIP);
+ }
+ }
+
+ @Override
+ public void onMovementBoundsChanged(boolean fromImeAdjustment) {}
+
+ @Override
+ public void onActionsChanged(ParceledListSlice<RemoteAction> actions) {
+ if (DEBUG) Log.d(TAG, "onActionsChanged()");
+
+ mTvPipMenuController.setAppActions(actions);
+ }
+ });
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to register pinned stack listener", e);
+ }
+ }
+
+ private static TaskInfo getPinnedTaskInfo() {
+ if (DEBUG) Log.d(TAG, "getPinnedTaskInfo()");
+ try {
+ final TaskInfo taskInfo = ActivityTaskManager.getService().getRootTaskInfo(
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
+ if (DEBUG) Log.d(TAG, " > taskInfo=" + taskInfo);
+ return taskInfo;
+ } catch (RemoteException e) {
+ Log.e(TAG, "getRootTaskInfo() failed", e);
+ return null;
+ }
+ }
+
+ private static void removeTask(int taskId) {
+ if (DEBUG) Log.d(TAG, "removeTask(), taskId=" + taskId);
+ try {
+ ActivityTaskManager.getService().removeTask(taskId);
+ } catch (Exception e) {
+ Log.e(TAG, "Atm.removeTask() failed", e);
+ }
+ }
+
+ private static String stateToName(@State int state) {
+ switch (state) {
+ case STATE_NO_PIP:
+ return "NO_PIP";
+ case STATE_PIP:
+ return "PIP";
+ case STATE_PIP_MENU:
+ return "PIP_MENU";
+ default:
+ // This can't happen.
+ throw new IllegalArgumentException("Unknown state " + state);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index 9192cf1..470ab0c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -42,7 +42,7 @@
*/
public class TvPipMenuController implements PipMenuController, TvPipMenuView.Listener {
private static final String TAG = "TvPipMenuController";
- private static final boolean DEBUG = PipController.DEBUG;
+ private static final boolean DEBUG = TvPipController.DEBUG;
private final Context mContext;
private final SystemWindows mSystemWindows;
@@ -134,10 +134,18 @@
}
void hideMenu() {
- if (DEBUG) Log.d(TAG, "hideMenu()");
+ hideMenu(true);
+ }
- if (isMenuVisible()) {
- mMenuView.hide();
+ void hideMenu(boolean movePipWindow) {
+ if (DEBUG) Log.d(TAG, "hideMenu(), movePipWindow=" + movePipWindow);
+
+ if (!isMenuVisible()) {
+ return;
+ }
+
+ mMenuView.hide();
+ if (movePipWindow) {
mDelegate.movePipToNormalPosition();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index f7b76c1..e08ca52 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -53,7 +53,7 @@
*/
public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
private static final String TAG = "TvPipMenuView";
- private static final boolean DEBUG = PipController.DEBUG;
+ private static final boolean DEBUG = TvPipController.DEBUG;
private static final float DISABLED_ACTION_ALPHA = 0.54f;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipNotification.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
similarity index 62%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipNotification.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
index 5716c7f..ce4b608 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipNotification.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
@@ -19,14 +19,17 @@
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
+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.PackageManager;
-import android.content.res.Resources;
import android.graphics.Bitmap;
import android.media.MediaMetadata;
+import android.os.UserHandle;
import android.text.TextUtils;
+import android.util.Log;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.wm.shell.R;
@@ -39,22 +42,27 @@
* <p>Once it's created, it will manage the PIP notification UI by itself except for handling
* configuration changes.
*/
-public class PipNotification {
- private static final boolean DEBUG = PipController.DEBUG;
- private static final String TAG = "PipNotification";
+public class TvPipNotificationController {
+ private static final String TAG = "TvPipNotification";
+ private static final boolean DEBUG = TvPipController.DEBUG;
- private static final String NOTIFICATION_TAG = PipNotification.class.getSimpleName();
- public static final String NOTIFICATION_CHANNEL_TVPIP = "TPP";
+ // Referenced in com.android.systemui.util.NotificationChannels.
+ public static final String NOTIFICATION_CHANNEL = "TVPIP";
+ private static final String NOTIFICATION_TAG = "TvPip";
- static final String ACTION_MENU = "PipNotification.menu";
- static final String ACTION_CLOSE = "PipNotification.close";
+ private static final String ACTION_SHOW_PIP_MENU =
+ "com.android.wm.shell.pip.tv.notification.action.SHOW_PIP_MENU";
+ private static final String ACTION_CLOSE_PIP =
+ "com.android.wm.shell.pip.tv.notification.action.CLOSE_PIP";
+ private final Context mContext;
private final PackageManager mPackageManager;
private final NotificationManager mNotificationManager;
private final Notification.Builder mNotificationBuilder;
+ private final ActionBroadcastReceiver mActionBroadcastReceiver;
+ private Delegate mDelegate;
private String mDefaultTitle;
- private int mDefaultIconResId;
/** Package name for the application that owns PiP window. */
private String mPackageName;
@@ -62,32 +70,56 @@
private String mMediaTitle;
private Bitmap mArt;
- public PipNotification(Context context, PipMediaController pipMediaController) {
+ public TvPipNotificationController(Context context, PipMediaController pipMediaController) {
+ mContext = context;
mPackageManager = context.getPackageManager();
mNotificationManager = context.getSystemService(NotificationManager.class);
- mNotificationBuilder = new Notification.Builder(context, NOTIFICATION_CHANNEL_TVPIP)
+ mNotificationBuilder = new Notification.Builder(context, NOTIFICATION_CHANNEL)
.setLocalOnly(true)
.setOngoing(false)
.setCategory(Notification.CATEGORY_SYSTEM)
+ .setShowWhen(true)
+ .setSmallIcon(R.drawable.pip_icon)
.extend(new Notification.TvExtender()
- .setContentIntent(createPendingIntent(context, ACTION_MENU))
- .setDeleteIntent(createPendingIntent(context, ACTION_CLOSE)));
+ .setContentIntent(createPendingIntent(context, ACTION_SHOW_PIP_MENU))
+ .setDeleteIntent(createPendingIntent(context, ACTION_CLOSE_PIP)));
+
+ mActionBroadcastReceiver = new ActionBroadcastReceiver();
pipMediaController.addMetadataListener(this::onMediaMetadataChanged);
onConfigurationChanged(context);
}
+ void setDelegate(Delegate delegate) {
+ if (DEBUG) Log.d(TAG, "setDelegate(), delegate=" + delegate);
+ if (mDelegate != null) {
+ throw new IllegalStateException(
+ "The delegate has already been set and should not change.");
+ }
+ if (delegate == null) {
+ throw new IllegalArgumentException("The delegate must not be null.");
+ }
+
+ mDelegate = delegate;
+ }
+
void show(String packageName) {
+ if (mDelegate == null) {
+ throw new IllegalStateException("Delegate is not set.");
+ }
+
mPackageName = packageName;
update();
+ mActionBroadcastReceiver.register();
}
void dismiss() {
mNotificationManager.cancel(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP);
mNotified = false;
mPackageName = null;
+ mActionBroadcastReceiver.unregister();
}
private void onMediaMetadataChanged(MediaMetadata metadata) {
@@ -101,11 +133,9 @@
* Called by {@link PipController} when the configuration is changed.
*/
void onConfigurationChanged(Context context) {
- Resources res = context.getResources();
- mDefaultTitle = res.getString(R.string.pip_notification_unknown_title);
- mDefaultIconResId = R.drawable.pip_icon;
+ mDefaultTitle = context.getResources().getString(R.string.pip_notification_unknown_title);
if (mNotified) {
- // update notification
+ // Update the notification.
update();
}
}
@@ -113,9 +143,7 @@
private void update() {
mNotified = true;
mNotificationBuilder
- .setShowWhen(true)
.setWhen(System.currentTimeMillis())
- .setSmallIcon(mDefaultIconResId)
.setContentTitle(getNotificationTitle());
if (mArt != null) {
mNotificationBuilder.setStyle(new Notification.BigPictureStyle()
@@ -178,4 +206,45 @@
return PendingIntent.getBroadcast(context, 0, new Intent(action),
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
}
+
+ private class ActionBroadcastReceiver extends BroadcastReceiver {
+ final IntentFilter mIntentFilter;
+ {
+ mIntentFilter = new IntentFilter();
+ mIntentFilter.addAction(ACTION_CLOSE_PIP);
+ mIntentFilter.addAction(ACTION_SHOW_PIP_MENU);
+ }
+ boolean mRegistered = false;
+
+ void register() {
+ if (mRegistered) return;
+
+ mContext.registerReceiver(this, mIntentFilter, UserHandle.USER_ALL);
+ mRegistered = true;
+ }
+
+ void unregister() {
+ if (!mRegistered) return;
+
+ mContext.unregisterReceiver(this);
+ mRegistered = false;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (DEBUG) Log.d(TAG, "on(Broadcast)Receive(), action=" + action);
+
+ if (ACTION_SHOW_PIP_MENU.equals(action)) {
+ mDelegate.showPictureInPictureMenu();
+ } else if (ACTION_CLOSE_PIP.equals(action)) {
+ mDelegate.closePip();
+ }
+ }
+ }
+
+ interface Delegate {
+ void showPictureInPictureMenu();
+ void closePip();
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
similarity index 98%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 5213f6c..7ce71b0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell;
+package com.android.wm.shell.transition;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
@@ -42,6 +42,7 @@
import androidx.annotation.BinderThread;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -82,6 +83,7 @@
mPlayerImpl = new TransitionPlayerImpl();
}
+ /** Register this transition handler with Core */
public void register(ShellTaskOrganizer taskOrganizer) {
taskOrganizer.registerTransitionPlayer(mPlayerImpl);
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index 5ab1c39..8543850 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -44,6 +44,18 @@
}
@JvmOverloads
+fun LayersAssertion.appPairsDividerBecomesVisible(
+ bugId: Int = 0,
+ enabled: Boolean = bugId == 0
+) {
+ all("dividerLayerBecomesVisible") {
+ this.hidesLayer(FlickerTestBase.DOCKED_STACK_DIVIDER)
+ .then()
+ .showsLayer(FlickerTestBase.DOCKED_STACK_DIVIDER)
+ }
+}
+
+@JvmOverloads
fun LayersAssertion.dockedStackDividerIsVisible(
bugId: Int = 0,
enabled: Boolean = bugId == 0
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterLegacySplitScreenTest.kt
index b33fa55..85bf4a1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterLegacySplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterLegacySplitScreenTest.kt
@@ -21,7 +21,6 @@
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.dsl.runWithFlicker
-import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.canSplitScreen
import com.android.server.wm.flicker.helpers.exitSplitScreen
import com.android.server.wm.flicker.helpers.isInSplitScreen
@@ -35,6 +34,7 @@
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+
import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisible
import org.junit.Assert
@@ -59,8 +59,6 @@
rotationName: String,
rotation: Int
) : SplitScreenTestBase(rotationName, rotation) {
- private val letterBox = "Letterbox"
-
private val splitScreenSetup: FlickerBuilder
get() = FlickerBuilder(instrumentation).apply {
val testLaunchActivity = "launch_splitScreen_test_activity"
@@ -91,7 +89,6 @@
windowManagerTrace {
navBarWindowIsAlwaysVisible()
statusBarWindowIsAlwaysVisible()
- visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(launcherPackageName))
}
}
}
@@ -114,7 +111,8 @@
rotation, splitScreenApp.defaultWindowName, 169271943)
dockedStackDividerBecomesVisible()
visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(launcherPackageName, splitScreenApp.defaultWindowName)
+ listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
+ LIVE_WALLPAPER_PACKAGE_NAME)
)
}
windowManagerTrace {
@@ -148,7 +146,7 @@
rotation, secondaryApp.defaultWindowName, 169271943)
dockedStackDividerBecomesVisible()
visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(launcherPackageName, splitScreenApp.defaultWindowName,
+ listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
secondaryApp.defaultWindowName)
)
}
@@ -157,6 +155,7 @@
showsAppWindow(splitScreenApp.defaultWindowName)
.and().showsAppWindow(secondaryApp.defaultWindowName)
}
+ visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(LAUNCHER_PACKAGE_NAME))
}
}
}
@@ -181,85 +180,14 @@
layersTrace {
dockedStackDividerIsInvisible()
visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(launcherPackageName, nonResizeableApp.defaultWindowName)
+ listOf(LAUNCHER_PACKAGE_NAME, nonResizeableApp.defaultWindowName)
)
}
windowManagerTrace {
end {
hidesAppWindow(nonResizeableApp.defaultWindowName)
}
- }
- }
- }
- }
-
- @Test
- fun testNonResizeableWhenAlreadyInSplitScreenPrimary() {
- val testTag = "testNonResizeableWhenAlreadyInSplitScreenPrimary"
- runWithFlicker(splitScreenSetup) {
- withTestName { testTag }
- repeat {
- TEST_REPETITIONS
- }
- transitions {
- nonResizeableApp.launchViaIntent()
- splitScreenApp.launchViaIntent()
- uiDevice.launchSplitScreen()
- nonResizeableApp.reopenAppFromOverview()
- }
- assertions {
- layersTrace {
- dockedStackDividerIsInvisible()
- end("appsEndingBounds", enabled = false) {
- val displayBounds = WindowUtils.getDisplayBounds(rotation)
- this.hasVisibleRegion(nonResizeableApp.defaultWindowName, displayBounds)
- }
- visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(launcherPackageName, splitScreenApp.defaultWindowName,
- nonResizeableApp.defaultWindowName, letterBox)
- )
- }
- windowManagerTrace {
- end {
- showsAppWindow(nonResizeableApp.defaultWindowName)
- hidesAppWindow(splitScreenApp.defaultWindowName)
- }
- }
- }
- }
- }
-
- @Test
- fun testNonResizeableWhenAlreadyInSplitScreenSecondary() {
- val testTag = "testNonResizeableWhenAlreadyInSplitScreenSecondary"
- runWithFlicker(splitScreenSetup) {
- withTestName { testTag }
- repeat {
- TEST_REPETITIONS
- }
- transitions {
- splitScreenApp.launchViaIntent()
- uiDevice.launchSplitScreen()
- uiDevice.pressBack()
- nonResizeableApp.launchViaIntent()
- }
- assertions {
- layersTrace {
- dockedStackDividerIsInvisible()
- end("appsEndingBounds", enabled = false) {
- val displayBounds = WindowUtils.getDisplayBounds(rotation)
- this.hasVisibleRegion(nonResizeableApp.defaultWindowName, displayBounds)
- }
- visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(launcherPackageName, splitScreenApp.defaultWindowName,
- nonResizeableApp.defaultWindowName, letterBox)
- )
- }
- windowManagerTrace {
- end {
- showsAppWindow(nonResizeableApp.defaultWindowName)
- hidesAppWindow(splitScreenApp.defaultWindowName)
- }
+ visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(LAUNCHER_PACKAGE_NAME))
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottomTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottomTest.kt
index 573ffc6..9586fd1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottomTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottomTest.kt
@@ -22,16 +22,20 @@
import com.android.server.wm.flicker.Flicker
import com.android.server.wm.flicker.FlickerTestRunner
import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.helpers.StandardAppHelper
import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.helpers.buildTestTag
import com.android.server.wm.flicker.helpers.exitSplitScreen
import com.android.server.wm.flicker.helpers.exitSplitScreenFromBottom
import com.android.server.wm.flicker.helpers.isInSplitScreen
import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+
import com.android.server.wm.flicker.repetitions
+import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_PRIMARY_LABEL
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.testapp.Components
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -53,23 +57,24 @@
@JvmStatic
fun getParams(): Collection<Array<Any>> {
val instrumentation = InstrumentationRegistry.getInstrumentation()
- val testApp = StandardAppHelper(instrumentation,
- "com.android.wm.shell.flicker.testapp", "SimpleApp")
+ val splitScreenApp = SplitScreenHelper(instrumentation,
+ TEST_APP_SPLITSCREEN_PRIMARY_LABEL,
+ Components.SplitScreenActivity())
- // b/161435597 causes the test not to work on 90 degrees
- return FlickerTestRunnerFactory(instrumentation, listOf(Surface.ROTATION_0))
+ // TODO(b/162923992) Use of multiple segments of flicker spec for testing
+ return FlickerTestRunnerFactory(instrumentation,
+ listOf(Surface.ROTATION_0, Surface.ROTATION_90))
.buildTest { configuration ->
withTestName {
- buildTestTag("exitSplitScreenFromBottom", testApp,
+ buildTestTag("exitSplitScreenFromBottom", splitScreenApp,
configuration)
}
repeat { configuration.repetitions }
setup {
- test {
- device.wakeUpAndGoToHomeScreen()
- }
eachRun {
- testApp.open()
+ device.wakeUpAndGoToHomeScreen()
+ device.openQuickStepAndClearRecentAppsFromOverview()
+ splitScreenApp.launchViaIntent()
device.launchSplitScreen()
device.waitForIdle()
this.setRotation(configuration.endRotation)
@@ -77,12 +82,10 @@
}
teardown {
eachRun {
- testApp.exit()
- }
- test {
if (device.isInSplitScreen()) {
device.exitSplitScreen()
}
+ splitScreenApp.exit()
}
}
transitions {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenTest.kt
index c51c73a..84bfe945 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenTest.kt
@@ -82,7 +82,7 @@
}
layersTrace {
visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(launcherPackageName))
+ listOf(LAUNCHER_PACKAGE_NAME))
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreenTest.kt
new file mode 100644
index 0000000..e9d3eb7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreenTest.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.legacysplitscreen
+
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.dsl.runWithFlicker
+import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open app to split screen.
+ * To run this test: `atest WMShellFlickerTests:NonResizableDismissInLegacySplitScreenTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class NonResizableDismissInLegacySplitScreenTest(
+ rotationName: String,
+ rotation: Int
+) : SplitScreenTestBase(rotationName, rotation) {
+
+ @Test
+ fun testNonResizableDismissInLegacySplitScreenTest() {
+ val testTag = "testNonResizableDismissInLegacySplitScreenTest"
+
+ runWithFlicker(transitionSetup) {
+ withTestName { testTag }
+ repeat { SplitScreenHelper.TEST_REPETITIONS }
+ transitions {
+ nonResizeableApp.launchViaIntent()
+ splitScreenApp.launchViaIntent()
+ device.launchSplitScreen()
+ nonResizeableApp.reopenAppFromOverview()
+ }
+ assertions {
+ layersTrace {
+ dockedStackDividerIsInvisible()
+ end("appsEndingBounds", enabled = false) {
+ val displayBounds = WindowUtils.getDisplayBounds(rotation)
+ this.hasVisibleRegion(nonResizeableApp.defaultWindowName, displayBounds)
+ }
+ visibleLayersShownMoreThanOneConsecutiveEntry(
+ listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
+ nonResizeableApp.defaultWindowName, LETTER_BOX_NAME)
+ )
+ }
+ windowManagerTrace {
+ end {
+ showsAppWindow(nonResizeableApp.defaultWindowName)
+ hidesAppWindow(splitScreenApp.defaultWindowName)
+ }
+ }
+ }
+ }
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val supportedRotations = intArrayOf(Surface.ROTATION_0, Surface.ROTATION_90)
+ return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreenTest.kt
new file mode 100644
index 0000000..b5a36f5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreenTest.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.legacysplitscreen
+
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.dsl.runWithFlicker
+import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open app to split screen.
+ * To run this test: `atest WMShellFlickerTests:NonResizableLaunchInLegacySplitScreenTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class NonResizableLaunchInLegacySplitScreenTest(
+ rotationName: String,
+ rotation: Int
+) : SplitScreenTestBase(rotationName, rotation) {
+
+ @Test
+ fun testNonResizableLaunchInLegacySplitScreenTest() {
+ val testTag = "NonResizableLaunchInLegacySplitScreenTest"
+
+ runWithFlicker(transitionSetup) {
+ withTestName { testTag }
+ repeat { SplitScreenHelper.TEST_REPETITIONS }
+ transitions {
+ nonResizeableApp.launchViaIntent()
+ splitScreenApp.launchViaIntent()
+ device.launchSplitScreen()
+ nonResizeableApp.reopenAppFromOverview()
+ }
+ assertions {
+ layersTrace {
+ dockedStackDividerIsInvisible()
+ end("appsEndingBounds", enabled = false) {
+ val displayBounds = WindowUtils.getDisplayBounds(rotation)
+ this.hasVisibleRegion(nonResizeableApp.defaultWindowName, displayBounds)
+ }
+ visibleLayersShownMoreThanOneConsecutiveEntry(
+ listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
+ nonResizeableApp.defaultWindowName, LETTER_BOX_NAME)
+ )
+ }
+ windowManagerTrace {
+ end {
+ showsAppWindow(nonResizeableApp.defaultWindowName)
+ hidesAppWindow(splitScreenApp.defaultWindowName)
+ }
+ }
+ }
+ }
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val supportedRotations = intArrayOf(Surface.ROTATION_0, Surface.ROTATION_90)
+ return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreenTest.kt
index af03869..90577ef 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreenTest.kt
@@ -17,37 +17,22 @@
package com.android.wm.shell.flicker.legacysplitscreen
import android.platform.test.annotations.Presubmit
-import android.support.test.launcherhelper.LauncherStrategyFactory
import android.view.Surface
import androidx.test.filters.RequiresDevice
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.Flicker
-import com.android.server.wm.flicker.FlickerTestRunner
-import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.helpers.StandardAppHelper
-import com.android.server.wm.flicker.endRotation
-import com.android.server.wm.flicker.focusChanges
-import com.android.server.wm.flicker.helpers.buildTestTag
-import com.android.server.wm.flicker.helpers.exitSplitScreen
-import com.android.server.wm.flicker.helpers.isInSplitScreen
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.appWindowBecomesVisible
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
import com.android.server.wm.flicker.layerBecomesVisible
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.focusChanges
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.dsl.runWithFlicker
+import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.noUncoveredRegions
-import com.android.server.wm.flicker.repetitions
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.appPairsDividerBecomesVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.FixMethodOrder
+import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -56,85 +41,59 @@
* Test open app to split screen.
* To run this test: `atest WMShellFlickerTests:OpenAppToLegacySplitScreenTest`
*/
-@Presubmit
+// TODO: Add back to pre-submit when stable.
+//@Presubmit
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class OpenAppToLegacySplitScreenTest(
- testName: String,
- flickerSpec: Flicker
-) : FlickerTestRunner(testName, flickerSpec) {
+ rotationName: String,
+ rotation: Int
+) : SplitScreenTestBase(rotationName, rotation) {
+ @Test
+ fun OpenAppToLegacySplitScreenTest() {
+ val testTag = "OpenAppToLegacySplitScreenTest"
+
+ runWithFlicker(transitionSetup) {
+ withTestName { testTag }
+ repeat { SplitScreenHelper.TEST_REPETITIONS }
+ transitions {
+ splitScreenApp.launchViaIntent()
+ device.pressHome()
+ this.setRotation(rotation)
+ device.launchSplitScreen()
+ }
+ assertions {
+ windowManagerTrace {
+ visibleWindowsShownMoreThanOneConsecutiveEntry()
+ appWindowBecomesVisible(splitScreenApp.getPackage())
+ }
+
+ layersTrace {
+ navBarLayerIsAlwaysVisible(bugId = 140855415)
+ noUncoveredRegions(rotation, enabled = false)
+ statusBarLayerIsAlwaysVisible(bugId = 140855415)
+ visibleLayersShownMoreThanOneConsecutiveEntry(
+ listOf(LAUNCHER_PACKAGE_NAME))
+ appPairsDividerBecomesVisible()
+ layerBecomesVisible(splitScreenApp.getPackage())
+ }
+
+ eventLog {
+ focusChanges(splitScreenApp.`package`,
+ "recents_animation_input_consumer", "NexusLauncherActivity",
+ bugId = 151179149)
+ }
+ }
+ }
+ }
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<Array<Any>> {
- val instrumentation = InstrumentationRegistry.getInstrumentation()
- val launcherPackageName = LauncherStrategyFactory.getInstance(instrumentation)
- .launcherStrategy.supportedLauncherPackage
- val testApp = StandardAppHelper(instrumentation,
- "com.android.wm.shell.flicker.testapp", "SimpleApp")
-
- // b/161435597 causes the test not to work on 90 degrees
- return FlickerTestRunnerFactory(instrumentation, listOf(Surface.ROTATION_0))
- .buildTest { configuration ->
- withTestName {
- buildTestTag("appToSplitScreen", testApp, configuration)
- }
- repeat { configuration.repetitions }
- setup {
- test {
- device.wakeUpAndGoToHomeScreen()
- device.openQuickStepAndClearRecentAppsFromOverview()
- }
- eachRun {
- testApp.open()
- device.pressHome()
- this.setRotation(configuration.endRotation)
- }
- }
- teardown {
- eachRun {
- if (device.isInSplitScreen()) {
- device.exitSplitScreen()
- }
- }
- test {
- testApp.exit()
- }
- }
- transitions {
- device.launchSplitScreen()
- }
- assertions {
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- appWindowBecomesVisible(testApp.getPackage())
- }
-
- layersTrace {
- navBarLayerIsAlwaysVisible(bugId = 140855415)
- statusBarLayerIsAlwaysVisible()
- noUncoveredRegions(configuration.endRotation, enabled = false)
- navBarLayerRotatesAndScales(configuration.endRotation,
- bugId = 140855415)
- statusBarLayerRotatesScales(configuration.endRotation)
- visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(launcherPackageName))
-
- dockedStackDividerBecomesVisible()
- layerBecomesVisible(testApp.getPackage())
- }
-
- eventLog {
- focusChanges(testApp.`package`,
- "recents_animation_input_consumer", "NexusLauncherActivity",
- bugId = 151179149)
- }
- }
- }
+ val supportedRotations = intArrayOf(Surface.ROTATION_0, Surface.ROTATION_90)
+ return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/SplitScreenTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/SplitScreenTestBase.kt
index a536ec8..2b94c5f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/SplitScreenTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/SplitScreenTestBase.kt
@@ -17,6 +17,15 @@
package com.android.wm.shell.flicker.legacysplitscreen
import android.support.test.launcherhelper.LauncherStrategyFactory
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.exitSplitScreen
+import com.android.server.wm.flicker.helpers.isInSplitScreen
+import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
+import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import com.android.wm.shell.flicker.NonRotationTestBase
import com.android.wm.shell.flicker.TEST_APP_NONRESIZEABLE_LABEL
import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_PRIMARY_LABEL
@@ -37,6 +46,39 @@
protected val nonResizeableApp = SplitScreenHelper(instrumentation,
TEST_APP_NONRESIZEABLE_LABEL,
Components.NonResizeableActivity())
- protected val launcherPackageName = LauncherStrategyFactory.getInstance(instrumentation)
+
+ protected val LAUNCHER_PACKAGE_NAME = LauncherStrategyFactory.getInstance(instrumentation)
.launcherStrategy.supportedLauncherPackage
+ protected val LIVE_WALLPAPER_PACKAGE_NAME =
+ "com.breel.wallpapers18.soundviz.wallpaper.variations.SoundVizWallpaperV2"
+ protected val LETTER_BOX_NAME = "Letterbox"
+
+ protected val transitionSetup: FlickerBuilder
+ get() = FlickerBuilder(instrumentation).apply {
+ setup {
+ eachRun {
+ uiDevice.wakeUpAndGoToHomeScreen()
+ uiDevice.openQuickStepAndClearRecentAppsFromOverview()
+ }
+ }
+ teardown {
+ eachRun {
+ if (uiDevice.isInSplitScreen()) {
+ uiDevice.exitSplitScreen()
+ }
+ splitScreenApp.exit()
+ nonResizeableApp.exit()
+ }
+ }
+ assertions {
+ layersTrace {
+ navBarLayerIsAlwaysVisible()
+ statusBarLayerIsAlwaysVisible()
+ }
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ }
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt
index 75388bf..5258e90 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt
@@ -171,4 +171,4 @@
get() = tvExtensions?.getParcelable("delete_intent")
private fun StatusBarNotification.isPipNotificationWithTitle(expectedTitle: String): Boolean =
- tag == "PipNotification" && title == expectedTitle
\ No newline at end of file
+ tag == "TvPip" && title == expectedTitle
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedAnimationControllerTest.java
index a8a3a9f..17fc057 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedAnimationControllerTest.java
@@ -25,6 +25,8 @@
import androidx.test.filters.SmallTest;
+import com.android.wm.shell.common.ShellExecutor;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -49,11 +51,14 @@
@Mock
private SurfaceControl mMockLeash;
+ @Mock
+ private ShellExecutor mMainExecutor;
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mTutorialHandler = new OneHandedTutorialHandler(mContext);
+ mTutorialHandler = new OneHandedTutorialHandler(mContext, mMainExecutor);
mOneHandedAnimationController = new OneHandedAnimationController(mContext);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
index 20184bf..16d13f4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
@@ -25,6 +25,7 @@
import static org.mockito.Mockito.when;
import android.content.om.IOverlayManager;
+import android.os.Handler;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -33,6 +34,7 @@
import androidx.test.filters.SmallTest;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerImpl;
import org.junit.Before;
@@ -45,7 +47,6 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
public class OneHandedControllerTest extends OneHandedTestCase {
Display mDisplay;
OneHandedController mOneHandedController;
@@ -69,11 +70,16 @@
IOverlayManager mMockOverlayManager;
@Mock
TaskStackListenerImpl mMockTaskStackListener;
+ @Mock
+ ShellExecutor mMockShellMainExecutor;
+ @Mock
+ Handler mMockShellMainHandler;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mDisplay = mContext.getDisplay();
+ mTimeoutHandler = Mockito.spy(new OneHandedTimeoutHandler(mMockShellMainExecutor));
OneHandedController oneHandedController = new OneHandedController(
mContext,
mMockDisplayController,
@@ -82,10 +88,12 @@
mMockTouchHandler,
mMockTutorialHandler,
mMockGestureHandler,
+ mTimeoutHandler,
mMockOverlayManager,
- mMockTaskStackListener);
+ mMockTaskStackListener,
+ mMockShellMainExecutor,
+ mMockShellMainHandler);
mOneHandedController = Mockito.spy(oneHandedController);
- mTimeoutHandler = Mockito.spy(OneHandedTimeoutHandler.get());
when(mMockDisplayController.getDisplay(anyInt())).thenReturn(mDisplay);
when(mMockDisplayAreaOrganizer.isInOneHanded()).thenReturn(false);
@@ -97,7 +105,7 @@
mContext);
OneHandedDisplayAreaOrganizer displayAreaOrganizer = new OneHandedDisplayAreaOrganizer(
mContext, mMockDisplayController, animationController, mMockTutorialHandler,
- Runnable::run, mMockBackgroundOrganizer);
+ mMockBackgroundOrganizer, mMockShellMainExecutor);
assertThat(displayAreaOrganizer.isInOneHanded()).isFalse();
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
index 3d9fad9..6cfd0c4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
@@ -44,6 +44,7 @@
import androidx.test.filters.SmallTest;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ShellExecutor;
import org.junit.Before;
import org.junit.Test;
@@ -53,7 +54,6 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
static final int DISPLAY_WIDTH = 1000;
static final int DISPLAY_HEIGHT = 1000;
@@ -82,9 +82,8 @@
WindowContainerTransaction mMockWindowContainerTransaction;
@Mock
OneHandedBackgroundPanelOrganizer mMockBackgroundOrganizer;
-
- Handler mSpyUpdateHandler;
- Handler.Callback mUpdateCallback = (msg) -> false;
+ @Mock
+ ShellExecutor mMockShellMainExecutor;
@Before
public void setUp() throws Exception {
@@ -110,19 +109,17 @@
when(mMockLeash.getWidth()).thenReturn(DISPLAY_WIDTH);
when(mMockLeash.getHeight()).thenReturn(DISPLAY_HEIGHT);
- mDisplayAreaOrganizer = new OneHandedDisplayAreaOrganizer(mContext,
+ mDisplayAreaOrganizer = spy(new OneHandedDisplayAreaOrganizer(mContext,
mMockDisplayController,
mMockAnimationController,
mTutorialHandler,
- Runnable::run, mMockBackgroundOrganizer);
- mSpyUpdateHandler = spy(new Handler(OneHandedThread.get().getLooper(), mUpdateCallback));
- mDisplayAreaOrganizer.setUpdateHandler(mSpyUpdateHandler);
+ mMockBackgroundOrganizer,
+ mMockShellMainExecutor));
}
@Test
public void testOnDisplayAreaAppeared() {
mDisplayAreaOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
- mTestableLooper.processAllMessages();
verify(mMockAnimationController, never()).getAnimator(any(), any(), any());
}
@@ -130,31 +127,18 @@
@Test
public void testOnDisplayAreaVanished() {
mDisplayAreaOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
- mTestableLooper.processAllMessages();
mDisplayAreaOrganizer.onDisplayAreaVanished(mDisplayAreaInfo);
assertThat(mDisplayAreaOrganizer.mDisplayAreaMap).isEmpty();
}
@Test
- public void testScheduleOffset() {
- final int xOffSet = 0;
- final int yOffSet = 100;
- mDisplayAreaOrganizer.scheduleOffset(xOffSet, yOffSet);
- mTestableLooper.processAllMessages();
-
- verify(mSpyUpdateHandler).sendMessage(any());
- }
-
- @Test
public void testRotation_portrait_0_to_landscape_90() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 0 -> 90
mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_90,
mMockWindowContainerTransaction);
- mTestableLooper.processAllMessages();
-
- verify(mSpyUpdateHandler).sendMessage(any());
+ verify(mDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
}
@Test
@@ -163,9 +147,7 @@
// Rotate 0 -> 270
mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_270,
mMockWindowContainerTransaction);
- mTestableLooper.processAllMessages();
-
- verify(mSpyUpdateHandler).sendMessage(any());
+ verify(mDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
}
@Test
@@ -174,9 +156,7 @@
// Rotate 180 -> 90
mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_180, Surface.ROTATION_90,
mMockWindowContainerTransaction);
- mTestableLooper.processAllMessages();
-
- verify(mSpyUpdateHandler).sendMessage(any());
+ verify(mDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
}
@Test
@@ -185,9 +165,7 @@
// Rotate 180 -> 270
mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_180, Surface.ROTATION_270,
mMockWindowContainerTransaction);
- mTestableLooper.processAllMessages();
-
- verify(mSpyUpdateHandler).sendMessage(any());
+ verify(mDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
}
@Test
@@ -196,9 +174,7 @@
// Rotate 90 -> 0
mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_90, Surface.ROTATION_0,
mMockWindowContainerTransaction);
- mTestableLooper.processAllMessages();
-
- verify(mSpyUpdateHandler).sendMessage(any());
+ verify(mDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
}
@Test
@@ -207,9 +183,7 @@
// Rotate 90 -> 180
mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_90, Surface.ROTATION_180,
mMockWindowContainerTransaction);
- mTestableLooper.processAllMessages();
-
- verify(mSpyUpdateHandler).sendMessage(any());
+ verify(mDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
}
@Test
@@ -218,9 +192,7 @@
// Rotate 270 -> 0
mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_270, Surface.ROTATION_0,
mMockWindowContainerTransaction);
- mTestableLooper.processAllMessages();
-
- verify(mSpyUpdateHandler).sendMessage(any());
+ verify(mDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
}
@Test
@@ -229,9 +201,7 @@
// Rotate 270 -> 180
mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_270, Surface.ROTATION_180,
mMockWindowContainerTransaction);
- mTestableLooper.processAllMessages();
-
- verify(mSpyUpdateHandler).sendMessage(any());
+ verify(mDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
}
@Test
@@ -240,9 +210,7 @@
// Rotate 0 -> 0
mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_0,
mMockWindowContainerTransaction);
- mTestableLooper.processAllMessages();
-
- verify(mSpyUpdateHandler, never()).sendMessage(any());
+ verify(mDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt());
}
@Test
@@ -251,9 +219,7 @@
// Rotate 0 -> 180
mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_180,
mMockWindowContainerTransaction);
- mTestableLooper.processAllMessages();
-
- verify(mSpyUpdateHandler, never()).sendMessage(any());
+ verify(mDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt());
}
@Test
@@ -262,9 +228,7 @@
// Rotate 180 -> 180
mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_180, Surface.ROTATION_180,
mMockWindowContainerTransaction);
- mTestableLooper.processAllMessages();
-
- verify(mSpyUpdateHandler, never()).sendMessage(any());
+ verify(mDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt());
}
@Test
@@ -273,9 +237,7 @@
// Rotate 180 -> 0
mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_180, Surface.ROTATION_0,
mMockWindowContainerTransaction);
- mTestableLooper.processAllMessages();
-
- verify(mSpyUpdateHandler, never()).sendMessage(any());
+ verify(mDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt());
}
@Test
@@ -284,9 +246,7 @@
// Rotate 90 -> 90
mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_90, Surface.ROTATION_90,
mMockWindowContainerTransaction);
- mTestableLooper.processAllMessages();
-
- verify(mSpyUpdateHandler, never()).sendMessage(any());
+ verify(mDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt());
}
@Test
@@ -295,9 +255,7 @@
// Rotate 90 -> 270
mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_90, Surface.ROTATION_270,
mMockWindowContainerTransaction);
- mTestableLooper.processAllMessages();
-
- verify(mSpyUpdateHandler, never()).sendMessage(any());
+ verify(mDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt());
}
@Test
@@ -306,9 +264,7 @@
// Rotate 270 -> 270
mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_270, Surface.ROTATION_270,
mMockWindowContainerTransaction);
- mTestableLooper.processAllMessages();
-
- verify(mSpyUpdateHandler, never()).sendMessage(any());
+ verify(mDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt());
}
@Test
@@ -317,8 +273,6 @@
// Rotate 270 -> 90
mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_270, Surface.ROTATION_90,
mMockWindowContainerTransaction);
- mTestableLooper.processAllMessages();
-
- verify(mSpyUpdateHandler, never()).sendMessage(any());
+ verify(mDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt());
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedGestureHandlerTest.java
index fb417c8..e5f2ff7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedGestureHandlerTest.java
@@ -26,6 +26,7 @@
import androidx.test.filters.SmallTest;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ShellExecutor;
import org.junit.Before;
import org.junit.Ignore;
@@ -36,17 +37,20 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
public class OneHandedGestureHandlerTest extends OneHandedTestCase {
OneHandedTutorialHandler mTutorialHandler;
OneHandedGestureHandler mGestureHandler;
@Mock
DisplayController mMockDisplayController;
+ @Mock
+ ShellExecutor mMockShellMainExecutor;
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mTutorialHandler = new OneHandedTutorialHandler(mContext);
- mGestureHandler = new OneHandedGestureHandler(mContext, mMockDisplayController);
+ mTutorialHandler = new OneHandedTutorialHandler(mContext, mMockShellMainExecutor);
+ mGestureHandler = new OneHandedGestureHandler(mContext, mMockDisplayController,
+ mMockShellMainExecutor);
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedSettingsUtilTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedSettingsUtilTest.java
index 7c11138..f8c9d53 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedSettingsUtilTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedSettingsUtilTest.java
@@ -38,7 +38,6 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
public class OneHandedSettingsUtilTest extends OneHandedTestCase {
ContentResolver mContentResolver;
ContentObserver mContentObserver;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTimeoutHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTimeoutHandlerTest.java
index e2b70c3..9219f15 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTimeoutHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTimeoutHandlerTest.java
@@ -20,43 +20,46 @@
import static com.android.wm.shell.onehanded.OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS;
import static com.android.wm.shell.onehanded.OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_NEVER;
import static com.android.wm.shell.onehanded.OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_SHORT_IN_SECONDS;
-import static com.android.wm.shell.onehanded.OneHandedTimeoutHandler.ONE_HANDED_TIMEOUT_STOP_MSG;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.verify;
+import android.os.Looper;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
+import com.android.wm.shell.common.ShellExecutor;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
public class OneHandedTimeoutHandlerTest extends OneHandedTestCase {
- OneHandedTimeoutHandler mTimeoutHandler;
+ private OneHandedTimeoutHandler mTimeoutHandler;
+ private ShellExecutor mMainExecutor;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mTimeoutHandler = Mockito.spy(OneHandedTimeoutHandler.get());
- }
-
- @Test
- public void testTimeoutHandler_isNotNull() {
- assertThat(OneHandedTimeoutHandler.get()).isNotNull();
+ mMainExecutor = new TestShellExecutor();
+ mTimeoutHandler = Mockito.spy(new OneHandedTimeoutHandler(mMainExecutor));
}
@Test
public void testTimeoutHandler_getTimeout_defaultMedium() {
- assertThat(OneHandedTimeoutHandler.get().getTimeout()).isEqualTo(
+ assertThat(mTimeoutHandler.getTimeout()).isEqualTo(
ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS);
}
@@ -64,28 +67,29 @@
public void testTimeoutHandler_setNewTime_resetTimer() {
mTimeoutHandler.setTimeout(ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS);
verify(mTimeoutHandler).resetTimer();
- assertThat(mTimeoutHandler.sHandler.hasMessages(ONE_HANDED_TIMEOUT_STOP_MSG)).isNotNull();
+ assertTrue(mTimeoutHandler.hasScheduledTimeout());
}
@Test
public void testSetTimeoutNever_neverResetTimer() {
mTimeoutHandler.setTimeout(ONE_HANDED_TIMEOUT_NEVER);
- assertThat(!mTimeoutHandler.sHandler.hasMessages(ONE_HANDED_TIMEOUT_STOP_MSG)).isNotNull();
+ assertFalse(mTimeoutHandler.hasScheduledTimeout());
}
@Test
public void testSetTimeoutShort() {
mTimeoutHandler.setTimeout(ONE_HANDED_TIMEOUT_SHORT_IN_SECONDS);
verify(mTimeoutHandler).resetTimer();
- assertThat(mTimeoutHandler.sHandler.hasMessages(ONE_HANDED_TIMEOUT_STOP_MSG)).isNotNull();
+ assertThat(mTimeoutHandler.getTimeout()).isEqualTo(ONE_HANDED_TIMEOUT_SHORT_IN_SECONDS);
+ assertTrue(mTimeoutHandler.hasScheduledTimeout());
}
@Test
public void testSetTimeoutMedium() {
mTimeoutHandler.setTimeout(ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS);
verify(mTimeoutHandler).resetTimer();
- assertThat(mTimeoutHandler.sHandler.hasMessages(
- ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS)).isNotNull();
+ assertThat(mTimeoutHandler.getTimeout()).isEqualTo(ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS);
+ assertTrue(mTimeoutHandler.hasScheduledTimeout());
}
@Test
@@ -96,10 +100,38 @@
@Test
public void testDragging_shouldRemoveAndSendEmptyMessageDelay() {
- final boolean isDragging = true;
mTimeoutHandler.setTimeout(ONE_HANDED_TIMEOUT_LONG_IN_SECONDS);
mTimeoutHandler.resetTimer();
- TestableLooper.get(this).processAllMessages();
- assertThat(mTimeoutHandler.sHandler.hasMessages(ONE_HANDED_TIMEOUT_STOP_MSG)).isNotNull();
+ assertTrue(mTimeoutHandler.hasScheduledTimeout());
+ }
+
+ private class TestShellExecutor implements ShellExecutor {
+ private ArrayList<Runnable> mExecuted = new ArrayList<>();
+ private ArrayList<Runnable> mDelayed = new ArrayList<>();
+
+ @Override
+ public void execute(Runnable runnable) {
+ mExecuted.add(runnable);
+ }
+
+ @Override
+ public void executeDelayed(Runnable r, long delayMillis) {
+ mDelayed.add(r);
+ }
+
+ @Override
+ public void removeCallbacks(Runnable r) {
+ mDelayed.remove(r);
+ }
+
+ @Override
+ public boolean hasCallback(Runnable r) {
+ return mDelayed.contains(r);
+ }
+
+ @Override
+ public Looper getLooper() {
+ return Looper.myLooper();
+ }
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTouchHandlerTest.java
index c69e385..d3b02ca 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTouchHandlerTest.java
@@ -23,22 +23,30 @@
import androidx.test.filters.SmallTest;
+import com.android.wm.shell.common.ShellExecutor;
+
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
public class OneHandedTouchHandlerTest extends OneHandedTestCase {
- OneHandedTouchHandler mTouchHandler;
+ private OneHandedTouchHandler mTouchHandler;
+
+ @Mock
+ private OneHandedTimeoutHandler mTimeoutHandler;
+
+ @Mock
+ private ShellExecutor mMainExecutor;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mTouchHandler = new OneHandedTouchHandler();
+ mTouchHandler = new OneHandedTouchHandler(mTimeoutHandler, mMainExecutor);
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java
index b187dc9..c451b8b2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java
@@ -19,12 +19,14 @@
import static org.mockito.Mockito.verify;
import android.content.om.IOverlayManager;
+import android.os.Handler;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerImpl;
import org.junit.Before;
@@ -35,12 +37,12 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
public class OneHandedTutorialHandlerTest extends OneHandedTestCase {
@Mock
OneHandedTouchHandler mTouchHandler;
OneHandedTutorialHandler mTutorialHandler;
OneHandedGestureHandler mGestureHandler;
+ OneHandedTimeoutHandler mTimeoutHandler;
OneHandedController mOneHandedController;
@Mock
DisplayController mMockDisplayController;
@@ -52,12 +54,18 @@
IOverlayManager mMockOverlayManager;
@Mock
TaskStackListenerImpl mMockTaskStackListener;
+ @Mock
+ ShellExecutor mMockShellMainExecutor;
+ @Mock
+ Handler mMockShellMainHandler;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mTutorialHandler = new OneHandedTutorialHandler(mContext);
- mGestureHandler = new OneHandedGestureHandler(mContext, mMockDisplayController);
+ mTutorialHandler = new OneHandedTutorialHandler(mContext, mMockShellMainExecutor);
+ mTimeoutHandler = new OneHandedTimeoutHandler(mMockShellMainExecutor);
+ mGestureHandler = new OneHandedGestureHandler(mContext, mMockDisplayController,
+ mMockShellMainExecutor);
mOneHandedController = new OneHandedController(
getContext(),
mMockDisplayController,
@@ -66,8 +74,11 @@
mTouchHandler,
mTutorialHandler,
mGestureHandler,
+ mTimeoutHandler,
mMockOverlayManager,
- mMockTaskStackListener);
+ mMockTaskStackListener,
+ mMockShellMainExecutor,
+ mMockShellMainHandler);
}
@Test
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 70a55add..ee2d83b 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -196,8 +196,9 @@
static int IP_V4_LENGTH = 4;
static int IP_V6_LENGTH = 16;
-void DestroyCallback(const C2Buffer * /* buf */, void *arg) {
+void DestroyCallback(const C2Buffer * buf, void *arg) {
android::sp<android::MediaEvent> event = (android::MediaEvent *)arg;
+ android::Mutex::Autolock autoLock(event->mLock);
if (event->mLinearBlockObj != NULL) {
JNIEnv *env = android::AndroidRuntime::getJNIEnv();
env->DeleteWeakGlobalRef(event->mLinearBlockObj);
@@ -206,6 +207,7 @@
event->mAvHandleRefCnt--;
event->finalize();
+ event->decStrong(buf);
}
namespace android {
@@ -395,6 +397,7 @@
pC2Buffer->setInfo(info);
}
pC2Buffer->registerOnDestroyNotify(&DestroyCallback, this);
+ incStrong(pC2Buffer.get());
jobject linearBlock =
env->NewObject(
env->FindClass("android/media/MediaCodec$LinearBlock"),
@@ -4162,6 +4165,7 @@
ALOGD("Failed get MediaEvent");
return NULL;
}
+ android::Mutex::Autolock autoLock(mediaEventSp->mLock);
return mediaEventSp->getLinearBlock();
}
diff --git a/packages/Connectivity/service/Android.bp b/packages/Connectivity/service/Android.bp
index a26f715..c8f3bd3 100644
--- a/packages/Connectivity/service/Android.bp
+++ b/packages/Connectivity/service/Android.bp
@@ -14,8 +14,8 @@
// limitations under the License.
//
-cc_defaults {
- name: "libservice-connectivity-defaults",
+cc_library_shared {
+ name: "libservice-connectivity",
// TODO: build against the NDK (sdk_version: "30" for example)
cflags: [
"-Wall",
@@ -26,6 +26,7 @@
srcs: [
"jni/com_android_server_TestNetworkService.cpp",
"jni/com_android_server_connectivity_Vpn.cpp",
+ "jni/onload.cpp",
],
shared_libs: [
"libbase",
@@ -35,27 +36,11 @@
// addresses, and remove dependency on libnetutils.
"libnetutils",
],
-}
-
-cc_library_shared {
- name: "libservice-connectivity",
- defaults: ["libservice-connectivity-defaults"],
- srcs: [
- "jni/onload.cpp",
- ],
apex_available: [
- // TODO: move this library to the tethering APEX and remove libservice-connectivity-static
- // "com.android.tethering",
+ "com.android.tethering",
],
}
-// Static library linked into libservices.core until libservice-connectivity can be loaded from
-// the tethering APEX instead.
-cc_library_static {
- name: "libservice-connectivity-static",
- defaults: ["libservice-connectivity-defaults"],
-}
-
java_library {
name: "service-connectivity",
srcs: [
@@ -75,5 +60,6 @@
],
apex_available: [
"//apex_available:platform",
+ "com.android.tethering",
],
}
diff --git a/packages/PrintSpooler/res/values-or/strings.xml b/packages/PrintSpooler/res/values-or/strings.xml
index 7000b95..15cecd6 100644
--- a/packages/PrintSpooler/res/values-or/strings.xml
+++ b/packages/PrintSpooler/res/values-or/strings.xml
@@ -80,10 +80,10 @@
<item quantity="one"><xliff:g id="COUNT_0">%1$s</xliff:g>ଟି ପ୍ରିଣ୍ଟର୍ ଖୋଜିବା ପାଇଁ ଇନଷ୍ଟଲ୍ କରନ୍ତୁ</item>
</plurals>
<string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> ପ୍ରିଣ୍ଟ କରାଯାଉଛି"</string>
- <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> କ୍ୟାନ୍ସଲ୍ କରାଯାଉଛି"</string>
+ <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> ବାତିଲ୍ କରାଯାଉଛି"</string>
<string name="failed_notification_title_template" msgid="2256217208186530973">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> ପ୍ରିଣ୍ଟର୍ ତ୍ରୁଟି"</string>
<string name="blocked_notification_title_template" msgid="1175435827331588646">"ପ୍ରିଣ୍ଟର୍ ଦ୍ୱାରା ରୋକାଯାଇଥିବା <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
- <string name="cancel" msgid="4373674107267141885">"କ୍ୟାନ୍ସଲ୍"</string>
+ <string name="cancel" msgid="4373674107267141885">"ବାତିଲ୍"</string>
<string name="restart" msgid="2472034227037808749">"ରିଷ୍ଟାର୍ଟ କରନ୍ତୁ"</string>
<string name="no_connection_to_printer" msgid="2159246915977282728">"ପ୍ରିଣ୍ଟର୍କୁ କୌଣସି ସଂଯୋଗ ନାହିଁ"</string>
<string name="reason_unknown" msgid="5507940196503246139">"ଅଜଣା"</string>
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index a06bb93..e036d87 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -78,6 +78,7 @@
<uses-permission android:name="android.permission.REQUEST_NETWORK_SCORES" />
<uses-permission android:name="android.permission.CONTROL_VPN" />
<uses-permission android:name="android.permission.PEERS_MAC_ADDRESS"/>
+ <uses-permission android:name="android.permission.READ_WIFI_CREDENTIAL"/>
<!-- Physical hardware -->
<uses-permission android:name="android.permission.MANAGE_USB" />
<uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" />
diff --git a/packages/SystemUI/res-keyguard/values-ml/strings.xml b/packages/SystemUI/res-keyguard/values-ml/strings.xml
index 66d0ac7..8076159 100644
--- a/packages/SystemUI/res-keyguard/values-ml/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ml/strings.xml
@@ -38,7 +38,7 @@
<string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ചാർജ് ചെയ്യുന്നു"</string>
<string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • വേഗത്തിൽ ചാർജ് ചെയ്യുന്നു"</string>
<string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • പതുക്കെ ചാർജ് ചെയ്യുന്നു"</string>
- <string name="keyguard_plugged_in_charging_limited" msgid="1158086783302116604">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ബാറ്ററി നില ഒപ്റ്റിമൈസ് ചെയ്യുന്നു"</string>
+ <string name="keyguard_plugged_in_charging_limited" msgid="1158086783302116604">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ബാറ്ററിയുടെ ആയുസിനായി ഒപ്റ്റിമൈസ് ചെയ്യുന്നു"</string>
<string name="keyguard_low_battery" msgid="1868012396800230904">"നിങ്ങളുടെ ചാർജർ കണക്റ്റുചെയ്യുക."</string>
<string name="keyguard_instructions_when_pattern_disabled" msgid="8448804180089936954">"അൺലോക്കുചെയ്യാൻ മെനു അമർത്തുക."</string>
<string name="keyguard_network_locked_message" msgid="407096292844868608">"നെറ്റ്വർക്ക് ലോക്കുചെയ്തു"</string>
diff --git a/packages/SystemUI/res/layout/qs_paged_page_side_labels.xml b/packages/SystemUI/res/layout/qs_paged_page_side_labels.xml
new file mode 100644
index 0000000..c830773
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_paged_page_side_labels.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+<com.android.systemui.qs.SideLabelTileLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/tile_page"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="false"
+ android:clipToPadding="false" />
diff --git a/packages/SystemUI/res/layout/qs_paged_tile_layout_side_labels.xml b/packages/SystemUI/res/layout/qs_paged_tile_layout_side_labels.xml
new file mode 100644
index 0000000..efa2403
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_paged_tile_layout_side_labels.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+
+<com.android.systemui.qs.PagedTileLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/qs_pager"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:clipChildren="true"
+ android:paddingBottom="@dimen/qs_paged_tile_layout_padding_bottom"
+ systemui:sideLabels="true" />
diff --git a/packages/SystemUI/res/layout/qs_tile_label.xml b/packages/SystemUI/res/layout/qs_tile_label.xml
index 81d44cf..571cbbc 100644
--- a/packages/SystemUI/res/layout/qs_tile_label.xml
+++ b/packages/SystemUI/res/layout/qs_tile_label.xml
@@ -34,7 +34,8 @@
<Space
android:id="@+id/expand_space"
android:layout_width="22dp"
- android:layout_height="0dp" />
+ android:layout_height="0dp"
+ android:visibility="gone" />
<TextView
android:id="@+id/tile_label"
diff --git a/packages/SystemUI/res/layout/qs_tile_label_divider.xml b/packages/SystemUI/res/layout/qs_tile_label_divider.xml
new file mode 100644
index 0000000..0d6460c
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_tile_label_divider.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<View xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="1px"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_vertical"
+ android:layout_marginBottom="10dp"
+ android:layout_marginTop="10dp"
+ android:layout_marginStart="0dp"
+ android:layout_marginEnd="0dp"
+ android:background="?android:attr/textColorSecondary"
+/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index fe4ba63..0beb286 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Gebruiker"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Nuwe gebruiker"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Nie gekoppel nie"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Geen netwerk nie"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi af"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Wys profiel"</string>
<string name="user_add_user" msgid="4336657383006913022">"Voeg gebruiker by"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Nuwe gebruiker"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Beëindig gastesessie?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle programme en data in hierdie sessie sal uitgevee word."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Beëindig sessie"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Welkom terug, gas!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Wiil jy jou sessie voortsit?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Begin van voor af"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 9149aa6..f56e84a 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"ተጠቃሚ"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"አዲስ ተጠቃሚ"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"በይነመረብ"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"አልተገናኘም"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"ምንም አውታረ መረብ የለም"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi ጠፍቷል"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"መገለጫ አሳይ"</string>
<string name="user_add_user" msgid="4336657383006913022">"ተጠቃሚ አክል"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"አዲስ ተጠቃሚ"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"የእንግዳ ክፍለ-ጊዜ ይብቃ?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"በዚህ ክፍለ-ጊዜ ውስጥ ያሉ ሁሉም መተግበሪያዎች እና ውሂብ ይሰረዛሉ።"</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"ክፍለ-ጊዜን አብቃ"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"እንኳን በደህና ተመለሱ እንግዳ!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"ክፍለ-ጊዜዎን መቀጠል ይፈልጋሉ?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"እንደገና ጀምር"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 0146fcd..aaaf778 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -357,8 +357,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"المستخدم"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"مستخدم جديد"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"الإنترنت"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"ليست متصلة"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"لا تتوفر شبكة"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"إيقاف Wi-Fi"</string>
@@ -464,11 +463,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"عرض الملف الشخصي"</string>
<string name="user_add_user" msgid="4336657383006913022">"إضافة مستخدم"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"مستخدم جديد"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"هل تريد إنهاء جلسة الضيف؟"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"سيتم حذف كل التطبيقات والبيانات في هذه الجلسة."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"إنهاء الجلسة"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"مرحبًا بك مجددًا في جلسة الضيف"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"هل تريد متابعة جلستك؟"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"البدء من جديد"</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 3c206f2..7e3e3cb 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -456,11 +456,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"প্ৰ\'ফাইল দেখুৱাওক"</string>
<string name="user_add_user" msgid="4336657383006913022">"ব্যৱহাৰকাৰী যোগ কৰক"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"নতুন ব্যৱহাৰকাৰী"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"অতিথিৰ ছেশ্বন সমাপ্ত কৰিবনে?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"এই ছেশ্বনৰ সকলো এপ্ আৰু ডেটা মচা হ\'ব।"</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"ছেশ্বন সমাপ্ত কৰক"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"আপোনাক পুনৰাই স্বাগতম জনাইছোঁ!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"আপুনি আপোনাৰ ছেশ্বন অব্যাহত ৰাখিব বিচাৰেনে?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"আকৌ আৰম্ভ কৰক"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 67e1718..6c22cc3 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"İstifadəçi"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Yeni istifadəçi"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"İnternet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Bağlantı yoxdur"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Şəbəkə yoxdur"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi sönülüdür"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Show profile"</string>
<string name="user_add_user" msgid="4336657383006913022">"İstifadəçi əlavə edin"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Yeni istifadəçi"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Qonaq sessiyası bitirilsin?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Bu sessiyada bütün tətbiqlər və data silinəcək."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Sessiyanı bitirin"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Xoş gəlmisiniz!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Sessiya davam etsin?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Yenidən başlayın"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index ae8ffbf..2f3d6d2 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -354,8 +354,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Korisnik"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Novi korisnik"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"WiFi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Veza nije uspostavljena"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Nema mreže"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"WiFi je isključen"</string>
@@ -458,11 +457,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Prikaži profil"</string>
<string name="user_add_user" msgid="4336657383006913022">"Dodaj korisnika"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Novi korisnik"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Želite da završite sesiju gosta?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Sve aplikacije i podaci u ovoj sesiji će biti izbrisani."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Završi sesiju"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Dobro došli nazad, goste!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Želite li da nastavite sesiju?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Počni iz početka"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index b90d57e..925e86a 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -355,8 +355,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Карыстальнік"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Новы карыстальнік"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Інтэрнэт"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Няма падключэння"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Няма сеткi"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi адключаны"</string>
@@ -460,11 +459,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Паказаць профіль"</string>
<string name="user_add_user" msgid="4336657383006913022">"Дадаць карыстальніка"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Новы карыстальнік"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Завяршыць гасцявы сеанс?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Усе праграмы і даныя гэтага сеанса будуць выдалены."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Завяршыць сеанс"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"З вяртаннем, госць!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Хочаце працягнуць сеанс?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Пачаць зноў"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index bfc8ef1..6a683c7 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Потребител"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Нов потребител"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Интернет"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Няма връзка"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Няма мрежа"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi е изключен"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Показване на потребителския профил"</string>
<string name="user_add_user" msgid="4336657383006913022">"Добавяне на потребител"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Нов потребител"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Да се прекрати ли сесията като гост?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Всички приложения и данни в тази сесия ще бъдат изтрити."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Прекратяване на сесията"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Добре дошли отново в сесията като гост!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Искате ли да продължите сесията си?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Започване отначало"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index c41a434..953ec00 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"ব্যবহারকারী"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"নতুন ব্যবহারকারী"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"ওয়াই-ফাই"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"ইন্টারনেট"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"সংযুক্ত নয়"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"কোনো নেটওয়ার্ক নেই"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"ওয়াই-ফাই বন্ধ"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"প্রোফাইল দেখান"</string>
<string name="user_add_user" msgid="4336657383006913022">"ব্যবহারকারী জুড়ুন"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"নতুন ব্যবহারকারী"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"গেস্ট সেশন শেষ করতে চান?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"এই সেশনের সব অ্যাপ্লিকেশান ও ডেটা মুছে ফেলা হবে।"</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"সেশন শেষ করুন"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"অতিথি, আপনি ফিরে আসায় আপনাকে স্বাগত!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"আপনি কি আপনার সেশনটি অবিরত রাখতে চান?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"আবার শুরু করুন"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 9a28773..6bf1852 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -354,8 +354,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Korisnik"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Novi korisnik"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"WiFi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Nije povezano"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Nema mreže"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"WiFi je isključen"</string>
@@ -458,11 +457,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Pokaži profil"</string>
<string name="user_add_user" msgid="4336657383006913022">"Dodaj korisnika"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Novi korisnik"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Završiti sesiju gosta?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Sve aplikacije i svi podaci iz ove sesije bit će izbrisani."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Završi sesiju"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Zdravo! Lijepo je opet vidjeti goste."</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Želite li nastaviti sesiju?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Počni ispočetka"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 426005a..71105cc 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Usuari"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Usuari nou"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Desconnectat"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"No hi ha cap xarxa"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi desconnectada"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Mostra el perfil"</string>
<string name="user_add_user" msgid="4336657383006913022">"Afegeix un usuari"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Usuari nou"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Vols finalitzar la sessió de convidat?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Totes les aplicacions i les dades d\'aquesta sessió se suprimiran."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Finalitza la sessió"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Benvingut de nou, convidat."</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Vols continuar amb la sessió?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Torna a començar"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 3e015ec..65e6767 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -355,8 +355,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Uživatel"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Nový uživatel"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Nepřipojeno"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Žádná síť"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi vypnuta"</string>
@@ -460,11 +459,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Zobrazit profil"</string>
<string name="user_add_user" msgid="4336657383006913022">"Přidat uživatele"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Nový uživatel"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Ukončit relaci hosta?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Veškeré aplikace a data v této relaci budou vymazána."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Ukončit relaci"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Vítejte zpět v relaci hosta!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Chcete v relaci pokračovat?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Začít znovu"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 8fd48b1..e50aa57 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Bruger"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Ny bruger"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Ikke forbundet"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Intet netværk"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi slået fra"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Vis profil"</string>
<string name="user_add_user" msgid="4336657383006913022">"Tilføj bruger"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Ny bruger"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Vil du afslutte gæstesessionen?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle apps og data i denne session slettes."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Afslut sessionen"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Velkommen tilbage, gæst!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Vil du fortsætte din session?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Start forfra"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 84b63ba..3cdebf9 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Nutzer"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Neuer Nutzer"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"WLAN"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Nicht verbunden"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Kein Netz"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"WLAN aus"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Profil öffnen"</string>
<string name="user_add_user" msgid="4336657383006913022">"Nutzer hinzufügen"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Neuer Nutzer"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Gastsitzung beenden?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle Apps und Daten in dieser Sitzung werden gelöscht."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Sitzung beenden"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Willkommen zurück im Gastmodus"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Möchtest du deine Sitzung fortsetzen?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Neu starten"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index bec78c0..c6ae7f4 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Χρήστης"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Νέος χρήστης"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Διαδίκτυο"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Μη συνδεδεμένο"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Κανένα δίκτυο"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi ανενεργό"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Εμφάνιση προφίλ"</string>
<string name="user_add_user" msgid="4336657383006913022">"Προσθήκη χρήστη"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Νέος χρήστης"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Λήξη περιόδου σύνδεσης επισκέπτη;"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Όλες οι εφαρμογές και τα δεδομένα αυτής της περιόδου σύνδεσης θα διαγραφούν."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Λήξη περιόδου σύνδεσης"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Επισκέπτη , καλώς όρισες ξανά!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Θέλετε να συνεχίσετε την περίοδο σύνδεσής σας;"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Έναρξη από την αρχή"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 21cac18..ea25ec6 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"User"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"New user"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Not Connected"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"No Network"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi Off"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index 7ac4c0d..a373a5c 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"User"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"New user"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Not Connected"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"No Network"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi Off"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 21cac18..ea25ec6 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"User"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"New user"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Not Connected"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"No Network"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi Off"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 21cac18..ea25ec6 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"User"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"New user"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Not Connected"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"No Network"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi Off"</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index 2f30c41..101d112 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"User"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"New user"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Not Connected"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"No Network"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi Off"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 7ef9cdb..1fc1609 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Usuario"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Usuario nuevo"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Sin conexión"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Sin red"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi desactivada"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 6c6b511..dd36a64 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Usuario"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Nuevo usuario"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"No conectado"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"No hay red."</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi desactivado"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index c64a07e..fb27be9 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Kasutaja"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Uus kasutaja"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"WiFi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Ühendus puudub"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Võrku pole"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"WiFi-ühendus on väljas"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Kuva profiil"</string>
<string name="user_add_user" msgid="4336657383006913022">"Lisa kasutaja"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Uus kasutaja"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Kas lõpetada külastajaseanss?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Seansi kõik rakendused ja andmed kustutatakse."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Lõpeta seanss"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Tere tulemast tagasi, külaline!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Kas soovite seansiga jätkata?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Alusta uuesti"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 5047cf1..d8b21c6 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Erabiltzailea"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Erabiltzaile berria"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wifia"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Konektatu gabe"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Ez dago sarerik"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi konexioa desaktibatuta"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 34c6bf6..df306f8 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"کاربر"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"کاربر جدید"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"اینترنت"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"متصل نیست"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"شبکهای موجود نیست"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi خاموش است"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"نمایش نمایه"</string>
<string name="user_add_user" msgid="4336657383006913022">"افزودن کاربر"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"کاربر جدید"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"جلسه مهمان تمام شود؟"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"همه برنامهها و دادههای این جلسه حذف خواهد شد."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"پایان جلسه"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"مهمان گرامی، بازگشتتان را خوش آمد میگوییم!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"آیا میخواهید جلسهتان را ادامه دهید؟"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"شروع مجدد"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 5486e39..4e0af37 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Käyttäjä"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Uusi käyttäjä"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Ei yhteyttä"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Ei verkkoa"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi-yhteys pois käytöstä"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Näytä profiili"</string>
<string name="user_add_user" msgid="4336657383006913022">"Lisää käyttäjä"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Uusi käyttäjä"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Lopetetaanko Vierailija-käyttökerta?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Kaikki sovellukset ja tämän istunnon tiedot poistetaan."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Lopeta käyttökerta"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Tervetuloa takaisin!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Haluatko jatkaa istuntoa?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Aloita alusta"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 63f65e0..8c944a4 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Utilisateur"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Nouvel utilisateur"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Non connecté"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Aucun réseau"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi désactivé"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Afficher le profil"</string>
<string name="user_add_user" msgid="4336657383006913022">"Ajouter un utilisateur"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Nouvel utilisateur"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Mettre fin à la session d\'invité?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toutes les applications et les données de cette session seront supprimées."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Fermer la session"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Bienvenue à nouveau dans la session Invité"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Voulez-vous poursuivre la session?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Recommencer"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index ebf199b..7ad239d 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Utilisateur"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Nouvel utilisateur"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Non connecté"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Aucun réseau"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi désactivé"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Afficher le profil"</string>
<string name="user_add_user" msgid="4336657383006913022">"Ajouter un utilisateur"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Nouvel utilisateur"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Fermer la session Invité ?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toutes les applications et les données de cette session seront supprimées."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Fermer la session"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Bienvenue à nouveau dans la session Invité"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Voulez-vous poursuivre la dernière session ?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Non, nouvelle session"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 36f7f4c..145b3c0 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Usuario"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Novo usuario"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wifi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Non conectada"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Non hai rede"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wifi desactivada"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Mostrar perfil"</string>
<string name="user_add_user" msgid="4336657383006913022">"Engadir usuario"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Novo usuario"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Queres finalizar a sesión de invitado?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Eliminaranse todas as aplicacións e datos desta sesión."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Finalizar sesión"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Benvido de novo, convidado."</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Queres continuar coa túa sesión?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Comezar de novo"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 66423e4..ea7af17 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"વપરાશકર્તા"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"નવો વપરાશકર્તા"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"વાઇ-ફાઇ"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"ઇન્ટરનેટ"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"કનેક્ટ થયેલ નથી"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"કોઈ નેટવર્ક નથી"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"વાઇ-ફાઇ બંધ"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"પ્રોફાઇલ બતાવો"</string>
<string name="user_add_user" msgid="4336657383006913022">"વપરાશકર્તા ઉમેરો"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"નવો વપરાશકર્તા"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"શું અતિથિ સત્ર સમાપ્ત કરીએ?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"આ સત્રમાંની તમામ ઍપ્લિકેશનો અને ડેટા કાઢી નાખવામાં આવશે."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"સત્ર સમાપ્ત કરો"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"ફરી સ્વાગત છે, અતિથિ!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"શું તમે તમારું સત્ર ચાલુ કરવા માંગો છો?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"શરૂ કરો"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 65cba2f..a8bed5b 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -355,8 +355,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"उपयोगकर्ता"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"नया उपयोगकर्ता"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"वाई-फ़ाई"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"इंटरनेट"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"कनेक्ट नहीं है"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"कोई नेटवर्क नहीं"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"वाई-फ़ाई बंद"</string>
@@ -458,11 +457,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"प्रोफ़ाइल दिखाएं"</string>
<string name="user_add_user" msgid="4336657383006913022">"उपयोगकर्ता जोड़ें"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"नया उपयोगकर्ता"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"मेहमान के तौर पर ब्राउज़ करने का सेशन खत्म करना चाहते हैं?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"इस सत्र के सभी ऐप्स और डेटा को हटा दिया जाएगा."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"सेशन खत्म करें"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"अतिथि, आपका फिर से स्वागत है!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"क्या आप अपना सत्र जारी रखना चाहते हैं?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"फिर से शुरू करें"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 7d60619..4ffa5b2 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -354,8 +354,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Korisnik"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Novi korisnik"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Nije povezano"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Nema mreže"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi isključen"</string>
@@ -458,11 +457,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Prikaz profila"</string>
<string name="user_add_user" msgid="4336657383006913022">"Dodavanje korisnika"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Novi korisnik"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Završiti gostujuću sesiju?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Sve aplikacije i podaci u ovoj sesiji bit će izbrisani."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Završi sesiju"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Dobro došli natrag, gostu!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Želite li nastaviti sesiju?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Počni ispočetka"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 2ee8913..cff5b0d 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Felhasználó"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Új felhasználó"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Nincs kapcsolat"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Nincs hálózat"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi kikapcsolva"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Profil megjelenítése"</string>
<string name="user_add_user" msgid="4336657383006913022">"Felhasználó hozzáadása"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Új felhasználó"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Befejezi a vendég munkamenetet?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"A munkamenetben található összes alkalmazás és adat törlődni fog."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Munkamenet befejezése"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Örülünk, hogy visszatért, vendég!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Folytatja a munkamenetet?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Újrakezdés"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 18af1ea..91ffe73 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Օգտատեր"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Նոր օգտատեր"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Ինտերնետ"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Միացված չէ"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Ցանց չկա"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi-ը անջատված է"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 4a048bb..f9e7397 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Pengguna"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Pengguna baru"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Tidak Terhubung"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Tidak Ada Jaringan"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi Mati"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Tampilkan profil"</string>
<string name="user_add_user" msgid="4336657383006913022">"Tambahkan pengguna"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Pengguna baru"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Akhiri sesi tamu?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Semua aplikasi dan data di sesi ini akan dihapus."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Akhiri sesi"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Selamat datang kembali, tamu!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Lanjutkan sesi Anda?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Mulai ulang"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 10e11fc..db5dac43 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Notandi"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Nýr notandi"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Engin tenging"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Ekkert net"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Slökkt á Wi-Fi"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Sýna snið"</string>
<string name="user_add_user" msgid="4336657383006913022">"Bæta notanda við"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Nýr notandi"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Ljúka gestalotu?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Öllum forritum og gögnum í þessari lotu verður eytt."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Ljúka lotu"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Velkominn aftur, gestur!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Viltu halda áfram með lotuna?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Byrja upp á nýtt"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 24e3f47..86e65bb 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -63,7 +63,7 @@
<string name="usb_debugging_allow" msgid="1722643858015321328">"Consenti"</string>
<string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Debug USB non consentito"</string>
<string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"L\'utente che ha eseguito l\'accesso a questo dispositivo non può attivare il debug USB. Per utilizzare questa funzione, passa all\'utente principale."</string>
- <string name="wifi_debugging_title" msgid="7300007687492186076">"Consentire debug wireless su questa rete?"</string>
+ <string name="wifi_debugging_title" msgid="7300007687492186076">"Consentire il debug wireless su questa rete?"</string>
<string name="wifi_debugging_message" msgid="5461204211731802995">"Nome della rete (SSID)\n<xliff:g id="SSID_0">%1$s</xliff:g>\n\nIndirizzo Wi‑Fi (BSSID)\n<xliff:g id="BSSID_1">%2$s</xliff:g>"</string>
<string name="wifi_debugging_always" msgid="2968383799517975155">"Consenti sempre su questa rete"</string>
<string name="wifi_debugging_allow" msgid="4573224609684957886">"Consenti"</string>
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Utente"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Nuovo utente"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Non connessa"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Nessuna rete"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi disattivato"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Mostra profilo"</string>
<string name="user_add_user" msgid="4336657383006913022">"Aggiungi utente"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Nuovo utente"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Vuoi terminare la sessione Ospite?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Tutte le app e i dati di questa sessione verranno eliminati."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Termina sessione"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Bentornato, ospite."</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Vuoi continuare la sessione?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Ricomincia"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 76dbdf9..8548dae 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -355,8 +355,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"משתמש"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"משתמש חדש"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"אינטרנט"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"אין חיבור"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"אין רשת"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi כבוי"</string>
@@ -460,11 +459,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"הצג פרופיל"</string>
<string name="user_add_user" msgid="4336657383006913022">"הוספת משתמש"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"משתמש חדש"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"להפסיק את הגלישה כאורח?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"כל האפליקציות והנתונים בפעילות זו באתר יימחקו."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"הפסקת הגלישה"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"שמחים לראותך שוב!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"האם ברצונך להמשיך בפעילות באתר?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"ברצוני להתחיל מחדש"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index b4edfe5..6cd5608 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"ユーザー"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"新しいユーザー"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"インターネット"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"接続されていません"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"ネットワークなし"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi OFF"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"プロファイルを表示"</string>
<string name="user_add_user" msgid="4336657383006913022">"ユーザーを追加"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"新しいユーザー"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"ゲスト セッションを終了しますか?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"このセッションでのアプリとデータはすべて削除されます。"</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"セッションを終了"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"おかえりなさい、ゲストさん"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"セッションを続行しますか?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"最初から開始"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index 26bde65..52d9f0e 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"მომხმარებელი"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"ახალი მომხმარებელი"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"ინტერნეტი"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"არ არის დაკავშირებული."</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"ქსელი არ არის"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi გამორთულია"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"პროფილის ჩვენება"</string>
<string name="user_add_user" msgid="4336657383006913022">"მომხმარებლის დამატება"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"ახალი მომხმარებელი"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"დასრულდეს სტუმრის სესია?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ამ სესიის ყველა აპი და მონაცემი წაიშლება."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"სესიის დასრულება"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"სტუმარო, გვიხარია, რომ დაბრუნდით!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"გსურთ, თქვენი სესიის გაგრძელება?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"ხელახლა დაწყება"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 9ce0a29..fcf8743 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Пайдаланушы"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Жаңа пайдаланушы"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Интернет"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Жалғанбаған"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Желі жоқ"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi өшірулі"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Профильді көрсету"</string>
<string name="user_add_user" msgid="4336657383006913022">"Пайдаланушы қосу"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Жаңа пайдаланушы"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Қонақ сеансы аяқталсын ба?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Осы сеанстағы барлық қолданбалар мен деректер жойылады."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Сеансты аяқтау"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Қош келдіңіз, қонақ"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Сеансты жалғастыру керек пе?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Қайта бастау"</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 1e6c7fa6e..e92bf9b 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"អ្នកប្រើ"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"អ្នកប្រើថ្មី"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"អ៊ីនធឺណិត"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"មិនបានតភ្ជាប់"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"គ្មានបណ្ដាញ"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"វ៉ាយហ្វាយបានបិទ"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"បង្ហាញប្រវត្តិរូប"</string>
<string name="user_add_user" msgid="4336657383006913022">"បន្ថែមអ្នកប្រើ"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"អ្នកប្រើថ្មី"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"បញ្ចប់វគ្គភ្ញៀវឬ?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ទិន្នន័យ និងកម្មវិធីទាំងអស់ក្នុងសម័យនេះនឹងត្រូវបានលុប។"</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"បញ្ចប់វគ្គ"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"សូមស្វាគមន៍ការត្រឡប់មកវិញ, ភ្ញៀវ!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"តើអ្នកចង់បន្តសម័យរបស់អ្នក?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"ចាប់ផ្ដើម"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 2958b29..d7a0adc 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"ಬಳಕೆದಾರ"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"ಹೊಸ ಬಳಕೆದಾರರು"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"ವೈ-ಫೈ"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"ಇಂಟರ್ನೆಟ್"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"ಸಂಪರ್ಕಗೊಂಡಿಲ್ಲ"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"ನೆಟ್ವರ್ಕ್ ಇಲ್ಲ"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"ವೈ-ಫೈ ಆಫ್"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"ಪ್ರೊಫೈಲ್ ತೋರಿಸು"</string>
<string name="user_add_user" msgid="4336657383006913022">"ಬಳಕೆದಾರರನ್ನು ಸೇರಿಸಿ"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"ಹೊಸ ಬಳಕೆದಾರರು"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"ಅತಿಥಿ ಸೆಷನ್ ಅಂತ್ಯಗೊಳಿಸುವುದೇ?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ಈ ಸೆಷನ್ನಲ್ಲಿನ ಎಲ್ಲ ಅಪ್ಲಿಕೇಶನ್ಗಳು ಮತ್ತು ಡೇಟಾವನ್ನು ಅಳಿಸಲಾಗುತ್ತದೆ."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"ಸೆಷನ್ ಅಂತ್ಯಗೊಳಿಸಿ"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"ಮತ್ತೆ ಸುಸ್ವಾಗತ, ಅತಿಥಿ!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"ನಿಮ್ಮ ಸೆಷನ್ ಮುಂದುವರಿಸಲು ಇಚ್ಚಿಸುವಿರಾ?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"ಪ್ರಾರಂಭಿಸಿ"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index cd613b6..54bcfb9 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"사용자"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"신규 사용자"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"인터넷"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"연결되어 있지 않음"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"네트워크가 연결되지 않음"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi 꺼짐"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"프로필 표시"</string>
<string name="user_add_user" msgid="4336657383006913022">"사용자 추가"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"신규 사용자"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"게스트 세션을 종료하시겠습니까?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"이 세션에 있는 모든 앱과 데이터가 삭제됩니다."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"세션 종료"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"게스트 세션 다시 시작"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"세션을 계속 진행하시겠습니까?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"다시 시작"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index e605341..d513380 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -355,8 +355,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Колдонуучу"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Жаңы колдонуучу"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Интернет"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Байланышкан жок"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Желе жок"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi өчүк"</string>
@@ -458,11 +457,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Профилди көрсөтүү"</string>
<string name="user_add_user" msgid="4336657383006913022">"Колдонуучу кошуу"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Жаңы колдонуучу"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Конок сеансы бүтүрүлсүнбү?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Бул сеанстагы бардык колдонмолор жана дайындар өчүрүлөт."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Сеансты бүтүрүү"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Кайтып келишиңиз менен, конок!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Сеансыңызды улантасызбы?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Кайра баштоо"</string>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index b8cc3de..7e595cd 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"ຜູ້ໃຊ້"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"ຜູ່ໃຊ້ໃໝ່"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"ອິນເຕີເນັດ"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"ບໍ່ໄດ້ເຊື່ອມຕໍ່"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"ບໍ່ມີເຄືອຂ່າຍ"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi ປິດ"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"ສະແດງໂປຣໄຟລ໌"</string>
<string name="user_add_user" msgid="4336657383006913022">"ເພີ່ມຜູ້ໃຊ້"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"ຜູ່ໃຊ້ໃໝ່"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"ສິ້ນສຸດເຊດຊັນແຂກບໍ?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ແອັບຯແລະຂໍ້ມູນທັງໝົດໃນເຊດຊັນນີ້ຈະຖືກລຶບອອກ."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"ສິ້ນສຸດເຊດຊັນ"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"ຍິນດີຕ້ອນຮັບກັບມາ, ຜູ່ຢ້ຽມຢາມ!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"ທ່ານຕ້ອງການສືບຕໍ່ເຊດຊັນຂອງທ່ານບໍ່?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"ເລີ່ມຕົ້ນໃຫມ່"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 8a32436..e0c27a9 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -355,8 +355,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Naudotojas"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Naujas naudotojas"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internetas"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Neprisijungta"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Tinklo nėra"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"„Wi-Fi“ išjungta"</string>
@@ -460,11 +459,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Rodyti profilį"</string>
<string name="user_add_user" msgid="4336657383006913022">"Pridėti naudotoją"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Naujas naudotojas"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Baigti svečio sesiją?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Bus ištrintos visos šios sesijos programos ir duomenys."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Baigti sesiją"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Sveiki sugrįžę, svety!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Ar norite tęsti sesiją?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Pradėti iš naujo"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 56ce376..ff36f51 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -354,8 +354,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Lietotājs"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Jauns lietotājs"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internets"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Nav izveidots savienojums"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Nav tīkla"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi ir izslēgts"</string>
@@ -458,11 +457,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Parādīt profilu"</string>
<string name="user_add_user" msgid="4336657383006913022">"Lietotāja pievienošana"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Jauns lietotājs"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Vai beigt viesa sesiju?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Tiks dzēstas visas šīs sesijas lietotnes un dati."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Beigt sesiju"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Laipni lūdzam atpakaļ, viesi!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Vai vēlaties turpināt savu sesiju?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Sākt no sākuma"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index fc83bb7..203d8b9 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Корисник"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Нов корисник"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Интернет"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Не е поврзано"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Нема мрежа"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi е исклучено"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Прикажи го профилот"</string>
<string name="user_add_user" msgid="4336657383006913022">"Додај корисник"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Нов корисник"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Да се заврши гостинската сесија?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Сите апликации и податоци во сесијата ќе се избришат."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Заврши ја сесијата"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Добре дојде пак, гостине!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Дали сакате да продолжите со сесијата?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Почни одново"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 9cd0412..77925ae 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"ഉപയോക്താവ്"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"പുതിയ ഉപയോക്താവ്"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"വൈഫൈ"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"ഇന്റർനെറ്റ്"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"കണക്റ്റ് ചെയ്തിട്ടില്ല"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"നെറ്റ്വർക്ക് ഒന്നുമില്ല"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"വൈഫൈ ഓഫുചെയ്യുക"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"പ്രൊഫൈൽ കാണിക്കുക"</string>
<string name="user_add_user" msgid="4336657383006913022">"ഉപയോക്താവിനെ ചേര്ക്കുക"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"പുതിയ ഉപയോക്താവ്"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"അതിഥി സെഷൻ അവസാനിപ്പിക്കണോ?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ഈ സെഷനിലെ എല്ലാ അപ്ലിക്കേഷനുകളും ഡാറ്റയും ഇല്ലാതാക്കും."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"സെഷൻ അവസാനിപ്പിക്കുക"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"അതിഥിയ്ക്ക് വീണ്ടും സ്വാഗതം!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"നിങ്ങളുടെ സെഷൻ തുടരണോ?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"പുനരാംരംഭിക്കുക"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index b0159b0..c772294 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Хэрэглэгч"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Шинэ хэрэглэгч"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Интернэт"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Холбогдоогүй"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Сүлжээгүй"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi унтарсан"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Профайлыг харуулах"</string>
<string name="user_add_user" msgid="4336657383006913022">"Хэрэглэгч нэмэх"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Шинэ хэрэглэгч"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Зочны сургалтыг дуусгах уу?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Энэ сешний бүх апп болон дата устах болно."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Сургалтыг дуусгах"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Тавтай морилно уу!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Та үргэлжлүүлэхийг хүсэж байна уу?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Дахин эхлүүлэх"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index b9b525e..d3e3601 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Pengguna"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Pengguna baharu"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Tidak Disambungkan"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Tiada Rangkaian"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi Dimatikan"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Tunjuk profil"</string>
<string name="user_add_user" msgid="4336657383006913022">"Tambah pengguna"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Pengguna baharu"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Tamatkan sesi tetamu?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Semua apl dan data dalam sesi ini akan dipadam."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Tamatkan sesi"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Selamat kembali, tetamu!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Adakah anda ingin meneruskan sesi anda?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Mulakan semula"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 9801ec6..0c1f9cb 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"အသုံးပြုသူ"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"အသုံးပြုသူ အသစ်"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"အင်တာနက်"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"ချိတ်ဆက်မထားပါ"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"ကွန်ရက်မရှိပါ"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"ဝိုင်ဖိုင်ပိတ်ရန်"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"ပရိုဖိုင်ကို ပြရန်"</string>
<string name="user_add_user" msgid="4336657383006913022">"အသုံးပြုသူ ထည့်ရန်"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"အသုံးပြုသူ အသစ်"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"ဧည့်သည်စက်ရှင်ကို အဆုံးသတ်မလား။"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ဒီချိတ်ဆက်မှု ထဲက အက်ပ်များ အားလုံး နှင့် ဒေတာကို ဖျက်ပစ်မည်။"</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"သတ်မှတ်ပေးထားသည့်အချိန် ပြီးဆုံးပြီ"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"ပြန်လာတာ ကြိုဆိုပါသည်၊ ဧည့်သည်!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"သင်သည် သင်၏ ချိတ်ဆက်မှုကို ဆက်ပြုလုပ် လိုပါသလား?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"အစမှ ပြန်စပါ"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 6724286..565e7fe 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Bruker"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Ny bruker"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internett"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Ikke tilkoblet"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Ingen nettverk"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi er av"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Vis profil"</string>
<string name="user_add_user" msgid="4336657383006913022">"Legg til brukere"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Ny bruker"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Vil du avslutte gjesteøkten?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle appene og all informasjon i denne økten slettes."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Avslutt økten"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Velkommen tilbake, gjest!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Vil du fortsette økten?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Start på nytt"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index c104ffd..9d95cf0 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"प्रयोगकर्ता"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"नयाँ प्रयोगकर्ता"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"इन्टरनेट"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"जोडिएको छैन"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"नेटवर्क छैन"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi बन्द"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"प्रोफाइल देखाउनुहोस्"</string>
<string name="user_add_user" msgid="4336657383006913022">"प्रयोगकर्ता थप्नुहोस्"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"नयाँ प्रयोगकर्ता"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"अतिथिको सत्र अन्त्य गर्ने हो?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"यस सत्रमा सबै एपहरू र डेटा मेटाइनेछ।"</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"सत्र अन्त्य गर्नुहोस्"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"पुनः स्वागत, अतिथि!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"तपाईं आफ्नो सत्र जारी गर्न चाहनुहुन्छ?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"सुरु गर्नुहोस्"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 7d87817..f598fa3 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Gebruiker"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Nieuwe gebruiker"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wifi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Niet verbonden"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Geen netwerk"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wifi uit"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Profiel weergeven"</string>
<string name="user_add_user" msgid="4336657383006913022">"Gebruiker toevoegen"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Nieuwe gebruiker"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Gastsessie beëindigen?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle apps en gegevens in deze sessie worden verwijderd."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Sessie beëindigen"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Welkom terug, gast!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Wil je doorgaan met je sessie?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Opnieuw starten"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index 7d7b84f..1e07dd4 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -456,11 +456,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"ପ୍ରୋଫାଇଲ୍ ଦେଖାନ୍ତୁ"</string>
<string name="user_add_user" msgid="4336657383006913022">"ଉପଯୋଗକର୍ତ୍ତାଙ୍କୁ ଯୋଗ କରନ୍ତୁ"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"ନୂଆ ଉପଯୋଗକର୍ତ୍ତା"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"ଅତିଥି ସେସନ୍ ଶେଷ କରିବେ?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ଏହି ଅବଧିର ସମସ୍ତ ଆପ୍ ଓ ଡାଟା ଡିଲିଟ୍ ହୋଇଯିବ।"</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"ସେସନ୍ ଶେଷ କରନ୍ତୁ"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"ପୁଣି ସ୍ୱାଗତ, ଅତିଥି!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"ଆପଣ ନିଜର ଅବଧି ଜାରି ରଖିବାକୁ ଚାହାନ୍ତି କି?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"ଆରମ୍ଭ କରନ୍ତୁ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 8a19e28..561e70d 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -355,8 +355,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Użytkownik"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Nowy użytkownik"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Brak połączenia"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Brak sieci"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi wyłączone"</string>
@@ -460,11 +459,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Pokaż profil"</string>
<string name="user_add_user" msgid="4336657383006913022">"Dodaj użytkownika"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Nowy użytkownik"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Zakończyć sesję gościa?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Wszystkie aplikacje i dane w tej sesji zostaną usunięte."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Zakończ sesję"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Witaj ponownie, gościu!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Chcesz kontynuować sesję?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Rozpocznij nową"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 57b924d..7fe53d3 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Usuário"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Novo usuário"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Não conectado"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Sem rede"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi desligado"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Mostrar perfil"</string>
<string name="user_add_user" msgid="4336657383006913022">"Adicionar usuário"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Novo usuário"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Encerrar sessão de visitante?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Todos os apps e dados nesta sessão serão excluídos."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Encerrar sessão"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Bem-vindo, convidado."</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Quer continuar a sessão?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Recomeçar"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index e39f757c..dd154e8 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Utilizador"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Novo utilizador"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Não Ligado"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Sem Rede"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi Desligado"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Mostrar perfil"</string>
<string name="user_add_user" msgid="4336657383006913022">"Adicionar utilizador"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Novo utilizador"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Pretende terminar a sessão de convidado?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Todas as aplicações e dados desta sessão serão eliminados."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Terminar sessão"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Bem-vindo de volta, caro(a) convidado(a)!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Pretende continuar a sessão?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Recomeçar"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 57b924d..7fe53d3 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Usuário"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Novo usuário"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Não conectado"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Sem rede"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi desligado"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Mostrar perfil"</string>
<string name="user_add_user" msgid="4336657383006913022">"Adicionar usuário"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Novo usuário"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Encerrar sessão de visitante?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Todos os apps e dados nesta sessão serão excluídos."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Encerrar sessão"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Bem-vindo, convidado."</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Quer continuar a sessão?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Recomeçar"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index acbe801..317fc09 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -354,8 +354,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Utilizator"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Utilizator nou"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Neconectată"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Nicio rețea"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi deconectat"</string>
@@ -458,11 +457,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Afișați profilul"</string>
<string name="user_add_user" msgid="4336657383006913022">"Adăugați un utilizator"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Utilizator nou"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Încheiați sesiunea pentru invitați?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toate aplicațiile și datele din această sesiune vor fi șterse."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Încheiați sesiunea"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Bine ați revenit în sesiunea pentru invitați!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Vreți să continuați sesiunea?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Începeți din nou"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index c59bd2ae..bbb014e 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -355,8 +355,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Пользователь"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Новый пользователь"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Интернет"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Нет соединения"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Нет сети"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi выкл."</string>
@@ -460,11 +459,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Показать профиль."</string>
<string name="user_add_user" msgid="4336657383006913022">"Добавить пользователя"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Новый пользователь"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Завершить гостевой сеанс?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Все приложения и данные этого профиля будут удалены."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Завершить сеанс"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Рады видеть вас снова!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Продолжить сеанс?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Начать заново"</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index a49ca86..a7a2bb7 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"පරිශීලක"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"නව පරිශීලකයා"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"අන්තර්ජාලය"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"සම්බන්ධ වී නොමැත"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"ජාලයක් නැත"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi අක්රියයි"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"පැතිකඩ පෙන්වන්න"</string>
<string name="user_add_user" msgid="4336657383006913022">"පරිශීලකයෙක් එක් කරන්න"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"නව පරිශීලකයා"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"ආරාධිත සැසිය අවසන් කරන්නද?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"මෙම සැසියේ සියළුම යෙදුම් සහ දත්ත මකාවී."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"සැසිය අවසන් කරන්න"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"නැවත සාදරයෙන් පිළිගනිමු, අමුත්තා!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"ඔබගේ සැසිය දිගටම කරගෙන යෑමට ඔබට අවශ්යද?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"යළි මුල සිට අරඹන්න"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index fe63b70..7eb5297 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -355,8 +355,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Používateľ"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Nový používateľ"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi‑Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Nepripojené"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Žiadna sieť"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Sieť Wi‑Fi je vypnutá"</string>
@@ -460,11 +459,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Zobraziť profil"</string>
<string name="user_add_user" msgid="4336657383006913022">"Pridať používateľa"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Nový používateľ"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Chcete ukončiť reláciu hosťa?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Všetky aplikácie a údaje v tejto relácii budú odstránené."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Ukončiť reláciu"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Hosť, vitajte späť!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Chcete v relácii pokračovať?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Začať odznova"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 85956d6..6d23f26 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -355,8 +355,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Uporabnik"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Nov uporabnik"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Povezava ni vzpostavljena"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Ni omrežja"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi izklopljen"</string>
@@ -460,11 +459,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Prikaz profila"</string>
<string name="user_add_user" msgid="4336657383006913022">"Dodajanje uporabnika"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Nov uporabnik"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Želite končati sejo gosta?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Vse aplikacije in podatki v tej seji bodo izbrisani."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Končaj sejo"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Znova pozdravljeni, gost!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Želite nadaljevati sejo?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Začni znova"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 733db0e..7e89b93 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Përdoruesi"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Përdorues i ri"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Nuk është i lidhur"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Nuk ka rrjet"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi është i çaktivizuar"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index c5414a4..af5175c 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -354,8 +354,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Корисник"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Нови корисник"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"WiFi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Интернет"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Веза није успостављена"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Нема мреже"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"WiFi је искључен"</string>
@@ -458,11 +457,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Прикажи профил"</string>
<string name="user_add_user" msgid="4336657383006913022">"Додај корисника"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Нови корисник"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Желите да завршите сесију госта?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Све апликације и подаци у овој сесији ће бити избрисани."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Заврши сесију"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Добро дошли назад, госте!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Желите ли да наставите сесију?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Почни из почетка"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index d2c993a..41bca7c 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Användare"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Ny användare"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Ej ansluten"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Inget nätverk"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi av"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Visa profil"</string>
<string name="user_add_user" msgid="4336657383006913022">"Lägg till användare"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Ny användare"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Vill du avsluta gästsessionen?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alla appar och data i denna session kommer att raderas."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Avsluta session"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Välkommen tillbaka gäst!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Vill du fortsätta sessionen?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Börja om"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 29045f3..f122013 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Mtumiaji"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Mtumiaji mpya"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Intaneti"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Haijaunganishwa"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Hakuna Mtandao"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi Imezimwa"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Onyesha wasifu"</string>
<string name="user_add_user" msgid="4336657383006913022">"Ongeza mtumiaji"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Mtumiaji mpya"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Ungependa kumaliza kipindi cha mgeni?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Data na programu zote katika kipindi hiki zitafutwa."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Maliza kipindi"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Karibu tena, mwalikwa!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Je, unataka kuendelea na kipindi chako?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Anza tena"</string>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 2ca6720..c076a03 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"பயனர்"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"புதியவர்"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"வைஃபை"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"இணையம்"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"இணைக்கப்படவில்லை"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"நெட்வொர்க் இல்லை"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"வைஃபையை முடக்கு"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"சுயவிவரத்தைக் காட்டு"</string>
<string name="user_add_user" msgid="4336657383006913022">"பயனரைச் சேர்"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"புதியவர்"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"விருந்தினர் அமர்வை நிறைவுசெய்யவா?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"இந்த அமர்வின் எல்லா பயன்பாடுகளும், தரவும் நீக்கப்படும்."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"அமர்வை நிறைவுசெய்"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"நல்வரவு!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"உங்கள் அமர்வைத் தொடர விருப்பமா?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"மீண்டும் தொடங்கு"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 9ce5fc7..fca5107 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"వినియోగదారు"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"కొత్త వినియోగదారు"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"ఇంటర్నెట్"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"కనెక్ట్ చేయబడలేదు"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"నెట్వర్క్ లేదు"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi ఆఫ్లో ఉంది"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"ప్రొఫైల్ని చూపు"</string>
<string name="user_add_user" msgid="4336657383006913022">"వినియోగదారుని జోడించండి"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"కొత్త వినియోగదారు"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"గెస్ట్ సెషన్ను ముగించాలా?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ఈ సెషన్లోని అన్ని అనువర్తనాలు మరియు డేటా తొలగించబడతాయి."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"సెషన్ను ముగించు"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"పునఃస్వాగతం, అతిథి!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"మీరు మీ సెషన్ని కొనసాగించాలనుకుంటున్నారా?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"మొదటి నుండి ప్రారంభించు"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 900012e..c909d37 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"ผู้ใช้"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"ผู้ใช้ใหม่"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"อินเทอร์เน็ต"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"ไม่ได้เชื่อมต่อ"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"ไม่มีเครือข่าย"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"ปิด WiFi"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"แสดงโปรไฟล์"</string>
<string name="user_add_user" msgid="4336657383006913022">"เพิ่มผู้ใช้"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"ผู้ใช้ใหม่"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"จบเซสชันผู้เยี่ยมชมใช่ไหม"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ระบบจะลบแอปและข้อมูลทั้งหมดในเซสชันนี้"</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"จบเซสชัน"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"ยินดีต้อนรับท่านผู้เยี่ยมชมกลับมาอีกครั้ง!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"คุณต้องการอยู่ในเซสชันต่อไปไหม"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"เริ่มต้นใหม่"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index f531de5..2e75fa3 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"User"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Bagong user"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Hindi Nakakonekta"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Walang Network"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Naka-off ang Wi-Fi"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Ipakita ang profile"</string>
<string name="user_add_user" msgid="4336657383006913022">"Magdagdag ng user"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Bagong user"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Tapusin ang session ng bisita?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Ide-delete ang lahat ng app at data sa session na ito."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Tapusin ang session"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Maligayang pagbabalik, bisita!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Gusto mo bang ipagpatuloy ang iyong session?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Magsimulang muli"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index ba70531..98552b1 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Kullanıcı"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Yeni kullanıcı"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Kablosuz"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"İnternet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Bağlı Değil"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Ağ yok"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Kablosuz Kapalı"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Profili göster"</string>
<string name="user_add_user" msgid="4336657383006913022">"Kullanıcı ekle"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Yeni kullanıcı"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Misafir oturumu sonlandırılsın mı?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Bu oturumdaki tüm uygulamalar ve veriler silinecek."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Oturumu sonlandır"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Tekrar hoş geldiniz sayın misafir!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Oturumunuza devam etmek istiyor musunuz?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Baştan başla"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index b2ae569..b70d36a 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -355,8 +355,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Користувач"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Новий користувач"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Інтернет"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Не під’єднано."</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Немає мережі"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi вимкнено"</string>
@@ -460,11 +459,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Показати профіль"</string>
<string name="user_add_user" msgid="4336657383006913022">"Додати користувача"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Новий користувач"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Завершити сеанс у режимі \"Гість\"?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Усі додатки й дані з цього сеансу буде видалено."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Завершити сеанс"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"З поверненням!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Продовжити сеанс?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Почати знову"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 06a448b..bbadf6a 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"صارف"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"نیا صارف"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"انٹرنیٹ"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"مربوط نہیں ہے"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"کوئی نیٹ ورک نہیں ہے"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi آف ہے"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"پروفائل دکھائیں"</string>
<string name="user_add_user" msgid="4336657383006913022">"صارف کو شامل کریں"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"نیا صارف"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"مہمان سیشن ختم کریں؟"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"اس سیشن میں موجود سبھی ایپس اور ڈیٹا کو حذف کر دیا جائے گا۔"</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"سیشن ختم کریں"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"مہمان، پھر سے خوش آمدید!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"کیا آپ اپنا سیشن جاری رکھنا چاہتے ہیں؟"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"دوبارہ شروع کریں"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index e1add65..a20676d 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Foydalanuvchi"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Yangi foydalanuvchi"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Ulanmagan"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Tarmoq mavjud emas"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi o‘chiq"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 021044d..5de7df6a 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Người dùng"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Người dùng mới"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"Internet"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Chưa được kết nối"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Không có mạng nào"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Tắt Wi-Fi"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Hiển thị hồ sơ"</string>
<string name="user_add_user" msgid="4336657383006913022">"Thêm người dùng"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Người dùng mới"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Kết thúc phiên khách?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Tất cả ứng dụng và dữ liệu trong phiên này sẽ bị xóa."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Kết thúc phiên"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Chào mừng bạn trở lại!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Bạn có muốn tiếp tục phiên của mình không?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Bắt đầu lại"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 60acb83..7f3f394 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"用户"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"新用户"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"WLAN"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"互联网"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"未连接"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"无网络"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"WLAN:关闭"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"显示个人资料"</string>
<string name="user_add_user" msgid="4336657383006913022">"添加用户"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"新用户"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"要结束访客会话吗?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"此会话中的所有应用和数据都将被删除。"</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"结束会话"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"访客,欢迎回来!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"要继续您的会话吗?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"重新开始"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index db55580..485f1cf 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"使用者"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"新使用者"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"互聯網"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"未連線"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"沒有網絡"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi 關閉"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"顯示個人檔案"</string>
<string name="user_add_user" msgid="4336657383006913022">"加入使用者"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"新使用者"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"要結束訪客工作階段嗎?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"這個工作階段中的所有應用程式和資料都會被刪除。"</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"結束工作階段"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"訪客您好,歡迎回來!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"您要繼續您的工作階段嗎?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"重新開始"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 1487ad3..1edeacd 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"使用者"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"新使用者"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"網際網路"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"未連線"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"沒有網路"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"Wi-Fi 已關閉"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"顯示設定檔"</string>
<string name="user_add_user" msgid="4336657383006913022">"新增使用者"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"新使用者"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"要結束訪客工作階段嗎?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"這個工作階段中的所有應用程式和資料都會遭到刪除。"</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"結束工作階段"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"訪客你好,歡迎回來!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"你要繼續這個工作階段嗎?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"重新開始"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index b0f084b..58baebc 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -353,8 +353,7 @@
<string name="quick_settings_user_title" msgid="8673045967216204537">"Umsebenzisi"</string>
<string name="quick_settings_user_new_user" msgid="3347905871336069666">"Umsebenzisi omusha"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"I-Wi-Fi"</string>
- <!-- no translation found for quick_settings_internet_label (6603068555872455463) -->
- <skip />
+ <string name="quick_settings_internet_label" msgid="6603068555872455463">"I-inthanethi"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"Akuxhunyiwe"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"Ayikho inethiwekhi"</string>
<string name="quick_settings_wifi_off_label" msgid="4003379736176547594">"I-Wi-Fi icimile"</string>
@@ -456,11 +455,9 @@
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"Bonisa iphrofayela"</string>
<string name="user_add_user" msgid="4336657383006913022">"Engeza umsebenzisi"</string>
<string name="user_new_user_name" msgid="2019166282704195789">"Umsebenzisi omusha"</string>
- <!-- no translation found for guest_exit_guest_dialog_title (2034481024623462357) -->
- <skip />
+ <string name="guest_exit_guest_dialog_title" msgid="2034481024623462357">"Misa isikhathi sesihambeli?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Zonke izinhlelo zokusebenza nedatha kulesi sikhathi zizosuswa."</string>
- <!-- no translation found for guest_exit_guest_dialog_remove (8533184512885775423) -->
- <skip />
+ <string name="guest_exit_guest_dialog_remove" msgid="8533184512885775423">"Phothula iseshini"</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Siyakwamukela futhi, sivakashi!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Ingabe ufuna ukuqhubeka ngesikhathi sakho?"</string>
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Qala phansi"</string>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 897e390..4059b49 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -167,5 +167,9 @@
<attr name="android:drawable" />
<attr name="android:alpha" />
</declare-styleable>
+
+ <declare-styleable name="PagedTileLayout">
+ <attr name="sideLabels" format="boolean"/>
+ </declare-styleable>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 8888ad6..72dd724 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -519,6 +519,7 @@
Scaled @dimen/qs_page_indicator-width by .4f.
-->
<dimen name="qs_page_indicator_dot_width">6.4dp</dimen>
+ <dimen name="qs_tile_side_label_padding">6dp</dimen>
<dimen name="qs_tile_icon_size">24dp</dimen>
<dimen name="qs_tile_text_size">12sp</dimen>
<dimen name="qs_tile_divider_height">1dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index c289ca2..1036c99 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -18,6 +18,7 @@
import static android.media.AudioManager.ACTION_MICROPHONE_MUTE_CHANGED;
+import android.Manifest;
import android.app.AppOpsManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -30,6 +31,7 @@
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
+import android.provider.DeviceConfig;
import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
@@ -75,6 +77,8 @@
private final AppOpsManager mAppOps;
private final AudioManager mAudioManager;
private final LocationManager mLocationManager;
+ // TODO ntmyren: remove t
+ private final PackageManager mPackageManager;
// mLocationProviderPackages are cached and updated only occasionally
private static final long LOCATION_PROVIDER_UPDATE_FREQUENCY_MS = 30000;
@@ -127,6 +131,7 @@
mAudioManager = audioManager;
mMicMuted = audioManager.isMicrophoneMute();
mLocationManager = context.getSystemService(LocationManager.class);
+ mPackageManager = context.getPackageManager();
dumpManager.registerDumpable(TAG, this);
}
@@ -334,6 +339,16 @@
return mLocationProviderPackages.contains(packageName);
}
+ // TODO ntmyren: remove after teamfood is finished
+ private boolean shouldShowAppPredictor(String pkgName) {
+ if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, "permissions_hub_2_enabled",
+ false)) {
+ return false;
+ }
+ return mPackageManager.checkPermission(Manifest.permission.MANAGE_APP_PREDICTIONS, pkgName)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
/**
* Does the app-op, uid and package name, refer to an operation that should be shown to the
* user. Only specficic ops (like {@link AppOpsManager.OP_SYSTEM_ALERT_WINDOW}) or
@@ -353,8 +368,9 @@
|| appOpCode == AppOpsManager.OP_PHONE_CALL_MICROPHONE) {
return true;
}
-
- if (appOpCode == AppOpsManager.OP_CAMERA && isLocationProvider(packageName)) {
+ // TODO ntmyren: Replace this with more robust check if this moves beyond teamfood
+ if ((appOpCode == AppOpsManager.OP_CAMERA && isLocationProvider(packageName))
+ || shouldShowAppPredictor(packageName)) {
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
index 0053fea..e822dd5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
@@ -98,6 +98,7 @@
}
// Refresh state.
setIndex(mPosition >> 1);
+ requestLayout();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 321f732..eaf2123 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -8,6 +8,7 @@
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.graphics.Rect;
import android.os.Bundle;
import android.util.AttributeSet;
@@ -47,7 +48,7 @@
};
private final ArrayList<TileRecord> mTiles = new ArrayList<>();
- private final ArrayList<TilePage> mPages = new ArrayList<>();
+ private final ArrayList<TileLayout> mPages = new ArrayList<>();
private PageIndicator mPageIndicator;
private float mPageIndicatorPosition;
@@ -71,6 +72,7 @@
private int mMaxColumns = TileLayout.NO_MAX_COLUMNS;
private boolean mShowLabels = true;
+ private final boolean mSideLabels;
public PagedTileLayout(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -81,13 +83,18 @@
mLayoutOrientation = getResources().getConfiguration().orientation;
mLayoutDirection = getLayoutDirection();
mClippingRect = new Rect();
+
+ TypedArray t = context.getTheme().obtainStyledAttributes(
+ attrs, R.styleable.PagedTileLayout, 0, 0);
+ mSideLabels = t.getBoolean(R.styleable.PagedTileLayout_sideLabels, false);
+ t.recycle();
}
private int mLastMaxHeight = -1;
@Override
public void setShowLabels(boolean show) {
mShowLabels = show;
- for (TilePage p : mPages) {
+ for (TileLayout p : mPages) {
p.setShowLabels(show);
}
mDistributeTiles = true;
@@ -145,7 +152,7 @@
}
// This will dump to the ui log all the tiles that are visible in this page
- private void logVisibleTiles(TilePage page) {
+ private void logVisibleTiles(TileLayout page) {
for (int i = 0; i < page.mRecords.size(); i++) {
QSTile t = page.mRecords.get(i).tile;
mUiEventLogger.logWithInstanceId(QSEvent.QS_TILE_VISIBLE, 0, t.getMetricsSpec(),
@@ -161,7 +168,7 @@
}
private void updateListening() {
- for (TilePage tilePage : mPages) {
+ for (TileLayout tilePage : mPages) {
tilePage.setListening(tilePage.getParent() != null && mListening);
}
}
@@ -222,13 +229,14 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mPages.add(createTilePage());
+ mPages.add(createTileLayout());
mAdapter.notifyDataSetChanged();
}
- private TilePage createTilePage() {
- TilePage page = (TilePage) LayoutInflater.from(getContext())
- .inflate(R.layout.qs_paged_page, this, false);
+ private TileLayout createTileLayout() {
+ TileLayout page = (TileLayout) LayoutInflater.from(getContext())
+ .inflate(mSideLabels ? R.layout.qs_paged_page_side_labels
+ : R.layout.qs_paged_page, this, false);
page.setMinRows(mMinRows);
page.setMaxColumns(mMaxColumns);
page.setShowLabels(mShowLabels);
@@ -283,7 +291,7 @@
setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
int currentItem = getCurrentPageNumber();
for (int i = 0; i < mPages.size(); i++) {
- TilePage page = mPages.get(i);
+ TileLayout page = mPages.get(i);
page.setSelected(i == currentItem ? selected : false);
if (page.isSelected()) {
logVisibleTiles(page);
@@ -325,7 +333,7 @@
}
while (mPages.size() < numPages) {
if (DEBUG) Log.d(TAG, "Adding page");
- mPages.add(createTilePage());
+ mPages.add(createTileLayout());
}
while (mPages.size() > numPages) {
if (DEBUG) Log.d(TAG, "Removing page");
@@ -422,7 +430,7 @@
final int nRows = mPages.get(0).mRows;
for (int i = 0; i < mPages.size(); i++) {
- TilePage t = mPages.get(i);
+ TileLayout t = mPages.get(i);
t.mRows = nRows;
}
}
@@ -465,19 +473,19 @@
public int getNumVisibleTiles() {
if (mPages.size() == 0) return 0;
- TilePage currentPage = mPages.get(getCurrentPageNumber());
+ TileLayout currentPage = mPages.get(getCurrentPageNumber());
return currentPage.mRecords.size();
}
public void startTileReveal(Set<String> tileSpecs, final Runnable postAnimation) {
if (tileSpecs.isEmpty() || mPages.size() < 2 || getScrollX() != 0 || !beginFakeDrag()) {
// Do not start the reveal animation unless there are tiles to animate, multiple
- // TilePages available and the user has not already started dragging.
+ // TileLayouts available and the user has not already started dragging.
return;
}
final int lastPageNumber = mPages.size() - 1;
- final TilePage lastPage = mPages.get(lastPageNumber);
+ final TileLayout lastPage = mPages.get(lastPageNumber);
final ArrayList<Animator> bounceAnims = new ArrayList<>();
for (TileRecord tr : lastPage.mRecords) {
if (tileSpecs.contains(tr.tile.getTileSpec())) {
@@ -557,12 +565,6 @@
return mRecords.size() >= maxTiles();
}
- public int maxTiles() {
- // Each page should be able to hold at least one tile. If there's not enough room to
- // show even 1 or there are no tiles, it probably means we are in the middle of setting
- // up.
- return Math.max(mColumns * mRows, 1);
- }
}
private final PagerAdapter mAdapter = new PagerAdapter() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 65f174c..5eba147 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -26,6 +26,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.provider.Settings;
import android.util.AttributeSet;
import android.util.Pair;
import android.view.Gravity;
@@ -112,6 +113,7 @@
private int mMediaTotalBottomMargin;
private int mFooterMarginStartHorizontal;
private Consumer<Boolean> mMediaVisibilityChangedListener;
+ private final boolean mSideLabels;
public QSPanel(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -119,6 +121,8 @@
mMediaTotalBottomMargin = getResources().getDimensionPixelSize(
R.dimen.quick_settings_bottom_margin_media);
mContext = context;
+ mSideLabels = Settings.Secure.getInt(
+ mContext.getContentResolver(), "sysui_side_labels", 0) != 0;
setOrientation(VERTICAL);
@@ -174,8 +178,9 @@
/** */
public QSTileLayout createRegularTileLayout() {
if (mRegularTileLayout == null) {
- mRegularTileLayout = (QSTileLayout) LayoutInflater.from(mContext).inflate(
- R.layout.qs_paged_tile_layout, this, false);
+ mRegularTileLayout = (QSTileLayout) LayoutInflater.from(mContext)
+ .inflate(mSideLabels ? R.layout.qs_paged_tile_layout_side_labels
+ : R.layout.qs_paged_tile_layout, this, false);
}
return mRegularTileLayout;
}
@@ -748,7 +753,13 @@
if (needsDynamicRowsAndColumns()) {
newLayout.setMinRows(horizontal ? 2 : 1);
// Let's use 3 columns to match the current layout
- newLayout.setMaxColumns(horizontal ? 3 : TileLayout.NO_MAX_COLUMNS);
+ int columns;
+ if (mSideLabels) {
+ columns = horizontal ? 1 : 2;
+ } else {
+ columns = horizontal ? 3 : TileLayout.NO_MAX_COLUMNS;
+ }
+ newLayout.setMaxColumns(columns);
}
updateMargins(mediaHostView);
}
@@ -763,6 +774,10 @@
updatePadding();
}
+ boolean useSideLabels() {
+ return mSideLabels;
+ }
+
private class H extends Handler {
private static final int SHOW_DETAIL = 1;
private static final int SET_TILE_VISIBILITY = 2;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index cca0e1b..e2d7d20 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -90,14 +90,26 @@
@Override
public void setTiles() {
- List<QSTile> tiles = new ArrayList();
+ List<QSTile> tiles = new ArrayList<>();
for (QSTile tile : mHost.getTiles()) {
tiles.add(tile);
if (tiles.size() == mView.getNumQuickTiles()) {
break;
}
}
- super.setTiles(tiles, true);
+ if (mView.useSideLabels()) {
+ List<QSTile> newTiles = new ArrayList<>();
+ for (int i = 0; i < tiles.size(); i += 2) {
+ newTiles.add(tiles.get(i));
+ }
+ for (int i = 1; i < tiles.size(); i += 2) {
+ newTiles.add(tiles.get(i));
+ }
+ super.setTiles(newTiles, true);
+
+ } else {
+ super.setTiles(tiles, true);
+ }
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt
new file mode 100644
index 0000000..74a7ac1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs
+
+import android.content.Context
+import android.util.AttributeSet
+import com.android.systemui.R
+
+open class SideLabelTileLayout(context: Context, attrs: AttributeSet) : TileLayout(context, attrs) {
+
+ override fun updateResources(): Boolean {
+ return super.updateResources().also {
+ mResourceColumns = 2
+ mMaxAllowedRows = 4
+ mCellMarginHorizontal = (mCellMarginHorizontal * 1.2).toInt()
+ mCellMarginVertical = mCellMarginHorizontal
+ mMaxCellHeight = context.resources.getDimensionPixelSize(R.dimen.qs_quick_tile_size)
+ }
+ }
+
+ override fun setShowLabels(show: Boolean) { }
+
+ override fun isFull(): Boolean {
+ return mRecords.size >= maxTiles()
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index e38c931..911261a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -42,7 +42,7 @@
private final boolean mLessRows;
private int mMinRows = 1;
private int mMaxColumns = NO_MAX_COLUMNS;
- private int mResourceColumns;
+ protected int mResourceColumns;
public TileLayout(Context context) {
this(context, null);
@@ -216,7 +216,7 @@
return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
}
- private int getCellHeight() {
+ protected int getCellHeight() {
return mShowLabels ? mMaxCellHeight : mMaxCellHeight / 2;
}
@@ -260,4 +260,18 @@
public int getNumVisibleTiles() {
return mRecords.size();
}
+
+ public boolean isFull() {
+ return false;
+ }
+
+ /**
+ * @return The maximum number of tiles this layout can hold
+ */
+ public int maxTiles() {
+ // Each layout should be able to hold at least one tile. If there's not enough room to
+ // show even 1 or there are no tiles, it probably means we are in the middle of setting
+ // up.
+ return Math.max(mColumns * mRows, 1);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index e9d481b..ba71fa6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -49,6 +49,7 @@
import com.android.systemui.qs.tiles.WifiTile;
import com.android.systemui.qs.tiles.WorkModeTile;
import com.android.systemui.util.leak.GarbageMonitor;
+import com.android.systemui.util.settings.SecureSettings;
import javax.inject.Inject;
import javax.inject.Provider;
@@ -86,9 +87,12 @@
private final Lazy<QSHost> mQsHostLazy;
private final Provider<CustomTile.Builder> mCustomTileBuilderProvider;
+ private final boolean mSideLabels;
+
@Inject
public QSFactoryImpl(
Lazy<QSHost> qsHostLazy,
+ SecureSettings settings,
Provider<CustomTile.Builder> customTileBuilderProvider,
Provider<WifiTile> wifiTileProvider,
Provider<InternetTile> internetTileProvider,
@@ -115,6 +119,8 @@
mQsHostLazy = qsHostLazy;
mCustomTileBuilderProvider = customTileBuilderProvider;
+ mSideLabels = settings.getInt("sysui_side_labels", 0) != 0;
+
mWifiTileProvider = wifiTileProvider;
mInternetTileProvider = internetTileProvider;
mBluetoothTileProvider = bluetoothTileProvider;
@@ -218,6 +224,8 @@
QSIconView icon = tile.createTileView(context);
if (collapsedView) {
return new QSTileBaseView(context, icon, collapsedView);
+ } else if (mSideLabels) {
+ return new QSTileViewHorizontal(context, icon);
} else {
return new com.android.systemui.qs.tileimpl.QSTileView(context, icon);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
index 655e4e2..38e2ba4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
@@ -61,15 +61,15 @@
private final FrameLayout mIconFrame;
protected QSIconView mIcon;
protected RippleDrawable mRipple;
- private Drawable mTileBackground;
+ protected Drawable mTileBackground;
private String mAccessibilityClass;
private boolean mTileState;
private boolean mCollapsedView;
- private boolean mShowRippleEffect = true;
+ protected boolean mShowRippleEffect = true;
private float mStrokeWidthActive;
private float mStrokeWidthInactive;
- private final ImageView mBg;
+ protected final ImageView mBg;
private final int mColorActive;
private final int mColorInactive;
private final int mColorDisabled;
@@ -162,7 +162,7 @@
}
}
- private void updateRippleSize() {
+ protected void updateRippleSize() {
// center the touch feedback on the center of the icon, and dial it down a bit
final int cx = mIconFrame.getMeasuredWidth() / 2 + mIconFrame.getLeft();
final int cy = mIconFrame.getMeasuredHeight() / 2 + mIconFrame.getTop();
@@ -311,7 +311,7 @@
return mLocInScreen[1] >= -getHeight();
}
- private int getCircleColor(int state) {
+ protected int getCircleColor(int state) {
switch (state) {
case Tile.STATE_ACTIVE:
return mColorActive;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
index 6502066..2dbd2cf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
@@ -37,7 +37,6 @@
/** View that represents a standard quick settings tile. **/
public class QSTileView extends QSTileBaseView {
private static final int MAX_LABEL_LINES = 2;
- private static final boolean DUAL_TARGET_ALLOWED = false;
private View mDivider;
protected TextView mLabel;
protected TextView mSecondLine;
@@ -46,8 +45,10 @@
protected ViewGroup mLabelContainer;
private View mExpandIndicator;
private View mExpandSpace;
- private ColorStateList mColorLabelDefault;
+ protected ColorStateList mColorLabelActive;
+ protected ColorStateList mColorLabelInactive;
private ColorStateList mColorLabelUnavailable;
+ protected boolean mDualTargetAllowed = false;
public QSTileView(Context context, QSIconView icon) {
this(context, icon, false);
@@ -64,7 +65,8 @@
createLabel();
setOrientation(VERTICAL);
setGravity(Gravity.CENTER_HORIZONTAL | Gravity.TOP);
- mColorLabelDefault = Utils.getColorAttr(getContext(), android.R.attr.textColorPrimary);
+ mColorLabelActive = Utils.getColorAttr(getContext(), android.R.attr.textColorPrimary);
+ mColorLabelInactive = mColorLabelActive;
// The text color for unavailable tiles is textColorSecondary, same as secondaryLabel for
// contrast purposes
mColorLabelUnavailable = Utils.getColorAttr(getContext(),
@@ -118,8 +120,15 @@
protected void handleStateChanged(QSTile.State state) {
super.handleStateChanged(state);
if (!Objects.equals(mLabel.getText(), state.label) || mState != state.state) {
- mLabel.setTextColor(state.state == Tile.STATE_UNAVAILABLE ? mColorLabelUnavailable
- : mColorLabelDefault);
+ ColorStateList labelColor;
+ if (state.state == Tile.STATE_ACTIVE) {
+ labelColor = mColorLabelActive;
+ } else if (state.state == Tile.STATE_INACTIVE) {
+ labelColor = mColorLabelInactive;
+ } else {
+ labelColor = mColorLabelUnavailable;
+ }
+ mLabel.setTextColor(labelColor);
mState = state.state;
mLabel.setText(state.label);
}
@@ -128,9 +137,8 @@
mSecondLine.setVisibility(TextUtils.isEmpty(state.secondaryLabel) ? View.GONE
: View.VISIBLE);
}
- boolean dualTarget = DUAL_TARGET_ALLOWED && state.dualTarget;
- mExpandIndicator.setVisibility(dualTarget ? View.VISIBLE : View.GONE);
- mExpandSpace.setVisibility(dualTarget ? View.VISIBLE : View.GONE);
+ boolean dualTarget = mDualTargetAllowed && state.dualTarget;
+ handleExpand(dualTarget);
mLabelContainer.setContentDescription(dualTarget ? state.dualLabelContentDescription
: null);
if (dualTarget != mLabelContainer.isClickable()) {
@@ -142,6 +150,11 @@
mPadLock.setVisibility(state.disabledByPolicy ? View.VISIBLE : View.GONE);
}
+ protected void handleExpand(boolean dualTarget) {
+ mExpandIndicator.setVisibility(dualTarget ? View.VISIBLE : View.GONE);
+ mExpandSpace.setVisibility(dualTarget ? View.VISIBLE : View.GONE);
+ }
+
@Override
public void init(OnClickListener click, OnClickListener secondaryClick,
OnLongClickListener longClick) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt
new file mode 100644
index 0000000..2ef78c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tileimpl
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.graphics.Color
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.PaintDrawable
+import android.graphics.drawable.RippleDrawable
+import android.service.quicksettings.Tile.STATE_ACTIVE
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.LinearLayout
+import com.android.systemui.R
+import com.android.systemui.plugins.qs.QSIconView
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.tileimpl.QSTileImpl.getColorForState
+
+class QSTileViewHorizontal(
+ context: Context,
+ icon: QSIconView
+) : QSTileView(context, icon, false) {
+
+ private var paintDrawable: PaintDrawable? = null
+ private var divider: View? = null
+
+ init {
+ orientation = HORIZONTAL
+ mDualTargetAllowed = true
+ mBg.setImageDrawable(null)
+ createDivider()
+ mColorLabelActive = ColorStateList.valueOf(getColorForState(getContext(), STATE_ACTIVE))
+ }
+
+ override fun createLabel() {
+ super.createLabel()
+ findViewById<LinearLayout>(R.id.label_group)?.gravity = Gravity.START
+ mLabel.gravity = Gravity.START
+ mSecondLine.gravity = Gravity.START
+ val padding = context.resources.getDimensionPixelSize(R.dimen.qs_tile_side_label_padding)
+ mLabelContainer.setPadding(padding, padding, padding, padding)
+ (mLabelContainer.layoutParams as LayoutParams).gravity = Gravity.CENTER_VERTICAL
+ }
+
+ fun createDivider() {
+ divider = LayoutInflater.from(context).inflate(R.layout.qs_tile_label_divider, this, false)
+ val position = indexOfChild(mLabelContainer)
+ addView(divider, position)
+ }
+
+ override fun init(
+ click: OnClickListener?,
+ secondaryClick: OnClickListener?,
+ longClick: OnLongClickListener?
+ ) {
+ super.init(click, secondaryClick, longClick)
+ mLabelContainer.setOnClickListener {
+ longClick?.onLongClick(it)
+ }
+ mLabelContainer.isClickable = false
+ }
+
+ override fun updateRippleSize() {
+ }
+
+ override fun newTileBackground(): Drawable? {
+ val d = super.newTileBackground()
+ if (paintDrawable == null) {
+ paintDrawable = PaintDrawable(Color.WHITE).apply {
+ setCornerRadius(30f)
+ }
+ }
+ if (d is RippleDrawable) {
+ d.addLayer(paintDrawable)
+ return d
+ } else {
+ return paintDrawable
+ }
+ }
+
+ override fun setClickable(clickable: Boolean) {
+ super.setClickable(clickable)
+ background = mTileBackground
+ if (clickable && mShowRippleEffect) {
+ mRipple?.setHotspotBounds(left, top, right, bottom)
+ } else {
+ mRipple?.setHotspotBounds(0, 0, 0, 0)
+ }
+ }
+
+ override fun handleStateChanged(state: QSTile.State) {
+ super.handleStateChanged(state)
+ paintDrawable?.setTint(getCircleColor(state.state))
+ mSecondLine.setTextColor(mLabel.textColors)
+ mLabelContainer.background = null
+ divider?.backgroundTintList = mLabel.textColors
+ }
+
+ override fun handleExpand(dualTarget: Boolean) {}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index 4d89dea..19eac77 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -54,6 +54,7 @@
import com.android.systemui.statusbar.policy.NetworkController.IconState;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
import com.android.systemui.statusbar.policy.WifiIcons;
+import com.android.wifitrackerlib.WifiEntry;
import java.util.List;
@@ -80,12 +81,13 @@
StatusBarStateController statusBarStateController,
ActivityStarter activityStarter,
QSLogger qsLogger,
- NetworkController networkController
+ NetworkController networkController,
+ AccessPointController accessPointController
) {
super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController,
activityStarter, qsLogger);
mController = networkController;
- mWifiController = mController.getAccessPointController();
+ mWifiController = accessPointController;
mDetailAdapter = (WifiDetailAdapter) createDetailAdapter();
mController.observe(getLifecycle(), mSignalCallback);
}
@@ -325,7 +327,7 @@
NetworkController.AccessPointController.AccessPointCallback, QSDetailItems.Callback {
private QSDetailItems mItems;
- private AccessPoint[] mAccessPoints;
+ private WifiEntry[] mAccessPoints;
@Override
public CharSequence getTitle() {
@@ -366,8 +368,8 @@
}
@Override
- public void onAccessPointsChanged(final List<AccessPoint> accessPoints) {
- mAccessPoints = accessPoints.toArray(new AccessPoint[accessPoints.size()]);
+ public void onAccessPointsChanged(final List<WifiEntry> accessPoints) {
+ mAccessPoints = accessPoints.toArray(new WifiEntry[accessPoints.size()]);
filterUnreachableAPs();
updateItems();
@@ -376,15 +378,15 @@
/** Filter unreachable APs from mAccessPoints */
private void filterUnreachableAPs() {
int numReachable = 0;
- for (AccessPoint ap : mAccessPoints) {
- if (ap.isReachable()) numReachable++;
+ for (WifiEntry ap : mAccessPoints) {
+ if (isWifiEntryReachable(ap)) numReachable++;
}
if (numReachable != mAccessPoints.length) {
- AccessPoint[] unfiltered = mAccessPoints;
- mAccessPoints = new AccessPoint[numReachable];
+ WifiEntry[] unfiltered = mAccessPoints;
+ mAccessPoints = new WifiEntry[numReachable];
int i = 0;
- for (AccessPoint ap : unfiltered) {
- if (ap.isReachable()) mAccessPoints[i++] = ap;
+ for (WifiEntry ap : unfiltered) {
+ if (isWifiEntryReachable(ap)) mAccessPoints[i++] = ap;
}
}
}
@@ -397,8 +399,8 @@
@Override
public void onDetailItemClick(Item item) {
if (item == null || item.tag == null) return;
- final AccessPoint ap = (AccessPoint) item.tag;
- if (!ap.isActive()) {
+ final WifiEntry ap = (WifiEntry) item.tag;
+ if (ap.getConnectedState() == WifiEntry.CONNECTED_STATE_DISCONNECTED) {
if (mWifiController.connect(ap)) {
mHost.collapsePanels();
}
@@ -442,12 +444,12 @@
if (mAccessPoints != null) {
items = new Item[mAccessPoints.length];
for (int i = 0; i < mAccessPoints.length; i++) {
- final AccessPoint ap = mAccessPoints[i];
+ final WifiEntry ap = mAccessPoints[i];
final Item item = new Item();
item.tag = ap;
item.iconResId = mWifiController.getIcon(ap);
item.line1 = ap.getSsid();
- item.line2 = ap.isActive() ? ap.getSummary() : null;
+ item.line2 = ap.getSummary();
item.icon2 = ap.getSecurity() != AccessPoint.SECURITY_NONE
? R.drawable.qs_ic_wifi_lock
: -1;
@@ -457,4 +459,8 @@
mItems.setItems(items);
}
}
+
+ private static boolean isWifiEntryReachable(WifiEntry ap) {
+ return ap.getLevel() != WifiEntry.WIFI_LEVEL_UNREACHABLE;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index c2ba344..aa8d710 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -130,7 +130,7 @@
}
}
- private WindowManager.LayoutParams getWindowLayoutParams() {
+ protected WindowManager.LayoutParams getWindowLayoutParams() {
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
index 3811ca9..bbc4b78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
@@ -50,18 +50,26 @@
@Inject
public FeatureFlags(@Background Executor executor) {
DeviceConfig.addOnPropertiesChangedListener(
- "systemui",
+ /* namespace= */ "systemui",
executor,
this::onPropertiesChanged);
}
public boolean isNewNotifPipelineEnabled() {
- return getDeviceConfigFlag("notification.newpipeline.enabled", true);
+ return getDeviceConfigFlag("notification.newpipeline.enabled", /* defaultValue= */ true);
}
public boolean isNewNotifPipelineRenderingEnabled() {
return isNewNotifPipelineEnabled()
- && getDeviceConfigFlag("notification.newpipeline.rendering", false);
+ && getDeviceConfigFlag("notification.newpipeline.rendering", /* defaultValue= */
+ false);
+ }
+
+ /**
+ * Flag used for guarding development of b/171917882.
+ */
+ public boolean isTwoColumnNotificationShadeEnabled() {
+ return getDeviceConfigFlag("notification.twocolumn", /* defaultValue= */ false);
}
private void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
@@ -76,7 +84,7 @@
synchronized (mCachedDeviceConfigFlags) {
Boolean flag = mCachedDeviceConfigFlags.get(key);
if (flag == null) {
- flag = DeviceConfig.getBoolean("systemui", key, defaultValue);
+ flag = DeviceConfig.getBoolean(/* namespace= */ "systemui", key, defaultValue);
mCachedDeviceConfigFlags.put(key, flag);
}
return flag;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
index 53d0228..ab58286 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
@@ -16,25 +16,47 @@
package com.android.systemui.statusbar.policy;
-import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
-import android.net.wifi.WifiManager.ActionListener;
+import android.net.ConnectivityManager;
+import android.net.NetworkScoreManager;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.SimpleClock;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.util.IndentingPrintWriter;
import android.util.Log;
-import com.android.settingslib.wifi.AccessPoint;
-import com.android.settingslib.wifi.WifiTracker;
-import com.android.settingslib.wifi.WifiTracker.WifiListener;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.settings.UserTracker;
+import com.android.wifitrackerlib.WifiEntry;
+import com.android.wifitrackerlib.WifiPickerTracker;
import java.io.PrintWriter;
+import java.time.Clock;
+import java.time.ZoneOffset;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
public class AccessPointControllerImpl
- implements NetworkController.AccessPointController, WifiListener {
+ implements NetworkController.AccessPointController,
+ WifiPickerTracker.WifiPickerTrackerCallback, LifecycleOwner {
private static final String TAG = "AccessPointController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -44,24 +66,51 @@
private static final int[] ICONS = WifiIcons.WIFI_FULL_ICONS;
- private final Context mContext;
private final ArrayList<AccessPointCallback> mCallbacks = new ArrayList<AccessPointCallback>();
- private final WifiTracker mWifiTracker;
private final UserManager mUserManager;
+ private final Executor mMainExecutor;
+
+ private @Nullable WifiPickerTracker mWifiPickerTracker;
+ private WifiPickerTrackerFactory mWifiPickerTrackerFactory;
+
+ private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
private int mCurrentUser;
- public AccessPointControllerImpl(Context context) {
- mContext = context;
- mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- mWifiTracker = new WifiTracker(context, this, false, true);
- mCurrentUser = ActivityManager.getCurrentUser();
+ public AccessPointControllerImpl(
+ UserManager userManager,
+ UserTracker userTracker,
+ Executor mainExecutor,
+ WifiPickerTrackerFactory wifiPickerTrackerFactory
+ ) {
+ mUserManager = userManager;
+ mCurrentUser = userTracker.getUserId();
+ mMainExecutor = mainExecutor;
+ mWifiPickerTrackerFactory = wifiPickerTrackerFactory;
+ mMainExecutor.execute(() -> mLifecycle.setCurrentState(Lifecycle.State.CREATED));
+ }
+
+ /**
+ * Initializes the controller.
+ *
+ * Will create a WifiPickerTracker associated to this controller.
+ */
+ public void init() {
+ if (mWifiPickerTracker == null) {
+ mWifiPickerTracker = mWifiPickerTrackerFactory.create(this.getLifecycle(), this);
+ }
+ }
+
+ @NonNull
+ @Override
+ public Lifecycle getLifecycle() {
+ return mLifecycle;
}
@Override
protected void finalize() throws Throwable {
+ mMainExecutor.execute(() -> mLifecycle.setCurrentState(Lifecycle.State.DESTROYED));
super.finalize();
- mWifiTracker.onDestroy();
}
public boolean canConfigWifi() {
@@ -79,7 +128,7 @@
if (DEBUG) Log.d(TAG, "addCallback " + callback);
mCallbacks.add(callback);
if (mCallbacks.size() == 1) {
- mWifiTracker.onStart();
+ mMainExecutor.execute(() -> mLifecycle.setCurrentState(Lifecycle.State.STARTED));
}
}
@@ -89,37 +138,59 @@
if (DEBUG) Log.d(TAG, "removeCallback " + callback);
mCallbacks.remove(callback);
if (mCallbacks.isEmpty()) {
- mWifiTracker.onStop();
+ mMainExecutor.execute(() -> mLifecycle.setCurrentState(Lifecycle.State.CREATED));
}
}
@Override
public void scanForAccessPoints() {
- fireAcccessPointsCallback(mWifiTracker.getAccessPoints());
+ if (mWifiPickerTracker == null) {
+ fireAcccessPointsCallback(Collections.emptyList());
+ return;
+ }
+ List<WifiEntry> entries = mWifiPickerTracker.getWifiEntries();
+ WifiEntry connectedEntry = mWifiPickerTracker.getConnectedWifiEntry();
+ if (connectedEntry != null) {
+ entries.add(0, connectedEntry);
+ }
+ fireAcccessPointsCallback(entries);
}
@Override
- public int getIcon(AccessPoint ap) {
+ public int getIcon(WifiEntry ap) {
int level = ap.getLevel();
- return ICONS[level >= 0 ? level : 0];
+ return ICONS[Math.max(0, level)];
}
- public boolean connect(AccessPoint ap) {
+ /**
+ * Connects to a {@link WifiEntry} if it's saved or does not require security.
+ *
+ * If the entry is not saved and requires security, will trigger
+ * {@link AccessPointCallback#onSettingsActivityTriggered}.
+ * @param ap
+ * @return {@code true} if {@link AccessPointCallback#onSettingsActivityTriggered} is triggered
+ */
+ public boolean connect(WifiEntry ap) {
if (ap == null) return false;
- if (DEBUG) Log.d(TAG, "connect networkId=" + ap.getConfig().networkId);
+ if (DEBUG) {
+ if (ap.getWifiConfiguration() != null) {
+ Log.d(TAG, "connect networkId=" + ap.getWifiConfiguration().networkId);
+ } else {
+ Log.d(TAG, "connect to unsaved network " + ap.getTitle());
+ }
+ }
if (ap.isSaved()) {
- mWifiTracker.getManager().connect(ap.getConfig().networkId, mConnectListener);
+ ap.connect(mConnectCallback);
} else {
// Unknown network, need to add it.
- if (ap.getSecurity() != AccessPoint.SECURITY_NONE) {
+ if (ap.getSecurity() != WifiEntry.SECURITY_NONE) {
Intent intent = new Intent(Settings.ACTION_WIFI_SETTINGS);
- intent.putExtra(EXTRA_START_CONNECT_SSID, ap.getSsidStr());
+ intent.putExtra(EXTRA_START_CONNECT_SSID, ap.getSsid());
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
fireSettingsIntentCallback(intent);
return true;
} else {
- ap.generateOpenNetworkConfig();
- mWifiTracker.getManager().connect(ap.getConfig(), mConnectListener);
+ ap.connect(mConnectCallback);
}
}
return false;
@@ -131,39 +202,129 @@
}
}
- private void fireAcccessPointsCallback(List<AccessPoint> aps) {
+ private void fireAcccessPointsCallback(List<WifiEntry> aps) {
for (AccessPointCallback callback : mCallbacks) {
callback.onAccessPointsChanged(aps);
}
}
public void dump(PrintWriter pw) {
- mWifiTracker.dump(pw);
- }
-
- @Override
- public void onWifiStateChanged(int state) {
- }
-
- @Override
- public void onConnectedChanged() {
- fireAcccessPointsCallback(mWifiTracker.getAccessPoints());
- }
-
- @Override
- public void onAccessPointsChanged() {
- fireAcccessPointsCallback(mWifiTracker.getAccessPoints());
- }
-
- private final ActionListener mConnectListener = new ActionListener() {
- @Override
- public void onSuccess() {
- if (DEBUG) Log.d(TAG, "connect success");
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
+ ipw.println("AccessPointControllerImpl:");
+ ipw.increaseIndent();
+ ipw.println("Callbacks: " + Arrays.toString(mCallbacks.toArray()));
+ ipw.println("WifiPickerTracker: " + mWifiPickerTracker.toString());
+ if (mWifiPickerTracker != null && !mCallbacks.isEmpty()) {
+ ipw.println("Connected: " + mWifiPickerTracker.getConnectedWifiEntry());
+ ipw.println("Other wifi entries: "
+ + Arrays.toString(mWifiPickerTracker.getWifiEntries().toArray()));
+ } else if (mWifiPickerTracker != null) {
+ ipw.println("WifiPickerTracker not started, cannot get reliable entries");
}
+ ipw.decreaseIndent();
+ }
+ @Override
+ public void onWifiStateChanged() {
+ scanForAccessPoints();
+ }
+
+ @Override
+ public void onWifiEntriesChanged() {
+ scanForAccessPoints();
+ }
+
+ @Override
+ public void onNumSavedNetworksChanged() {
+ // Do nothing
+ }
+
+ @Override
+ public void onNumSavedSubscriptionsChanged() {
+ // Do nothing
+ }
+
+ private final WifiEntry.ConnectCallback mConnectCallback = new WifiEntry.ConnectCallback() {
@Override
- public void onFailure(int reason) {
- if (DEBUG) Log.d(TAG, "connect failure reason=" + reason);
+ public void onConnectResult(int status) {
+ if (status == CONNECT_STATUS_SUCCESS) {
+ if (DEBUG) Log.d(TAG, "connect success");
+ } else {
+ if (DEBUG) Log.d(TAG, "connect failure reason=" + status);
+ }
}
};
+
+ /**
+ * Factory for creating {@link WifiPickerTracker}.
+ *
+ * Uses the same time intervals as the Settings page for Wifi.
+ */
+ @SysUISingleton
+ public static class WifiPickerTrackerFactory {
+
+ // Max age of tracked WifiEntries
+ private static final long MAX_SCAN_AGE_MILLIS = 15_000;
+ // Interval between initiating WifiPickerTracker scans
+ private static final long SCAN_INTERVAL_MILLIS = 10_000;
+
+ private final Context mContext;
+ private final @Nullable WifiManager mWifiManager;
+ private final ConnectivityManager mConnectivityManager;
+ private final NetworkScoreManager mNetworkScoreManager;
+ private final Handler mMainHandler;
+ private final Handler mWorkerHandler;
+ private final Clock mClock = new SimpleClock(ZoneOffset.UTC) {
+ @Override
+ public long millis() {
+ return SystemClock.elapsedRealtime();
+ }
+ };
+
+ @Inject
+ public WifiPickerTrackerFactory(
+ Context context,
+ @Nullable WifiManager wifiManager,
+ ConnectivityManager connectivityManager,
+ NetworkScoreManager networkScoreManager,
+ @Main Handler mainHandler,
+ @Background Handler workerHandler
+ ) {
+ mContext = context;
+ mWifiManager = wifiManager;
+ mConnectivityManager = connectivityManager;
+ mNetworkScoreManager = networkScoreManager;
+ mMainHandler = mainHandler;
+ mWorkerHandler = workerHandler;
+ }
+
+ /**
+ * Create a {@link WifiPickerTracker}
+ *
+ * @param lifecycle
+ * @param listener
+ * @return a new {@link WifiPickerTracker} or {@code null} if {@link WifiManager} is null.
+ */
+ public @Nullable WifiPickerTracker create(
+ Lifecycle lifecycle,
+ WifiPickerTracker.WifiPickerTrackerCallback listener
+ ) {
+ if (mWifiManager == null) {
+ return null;
+ }
+ return new WifiPickerTracker(
+ lifecycle,
+ mContext,
+ mWifiManager,
+ mConnectivityManager,
+ mNetworkScoreManager,
+ mMainHandler,
+ mWorkerHandler,
+ mClock,
+ MAX_SCAN_AGE_MILLIS,
+ SCAN_INTERVAL_MILLIS,
+ listener
+ );
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index f92860b..b012dc4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -21,9 +21,9 @@
import android.telephony.SubscriptionInfo;
import com.android.settingslib.net.DataUsageController;
-import com.android.settingslib.wifi.AccessPoint;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.wifitrackerlib.WifiEntry;
import java.util.List;
@@ -123,12 +123,12 @@
void addAccessPointCallback(AccessPointCallback callback);
void removeAccessPointCallback(AccessPointCallback callback);
void scanForAccessPoints();
- int getIcon(AccessPoint ap);
- boolean connect(AccessPoint ap);
+ int getIcon(WifiEntry ap);
+ boolean connect(WifiEntry ap);
boolean canConfigWifi();
public interface AccessPointCallback {
- void onAccessPointsChanged(List<AccessPoint> accessPoints);
+ void onAccessPointsChanged(List<WifiEntry> accessPoints);
void onSettingsActivityTriggered(Intent settingsIntent);
}
}
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 e419966..5f5a83c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -187,6 +187,7 @@
TelephonyManager telephonyManager,
@Nullable WifiManager wifiManager,
NetworkScoreManager networkScoreManager,
+ AccessPointControllerImpl accessPointController,
DemoModeController demoModeController) {
this(context, connectivityManager,
telephonyManager,
@@ -194,7 +195,7 @@
networkScoreManager,
SubscriptionManager.from(context), Config.readConfig(context), bgLooper,
new CallbackHandler(),
- new AccessPointControllerImpl(context),
+ accessPointController,
new DataUsageController(context),
new SubscriptionDefaults(),
deviceProvisionedController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index 914105f..069b405 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -16,6 +16,12 @@
package com.android.systemui.statusbar.policy.dagger;
+import android.os.UserManager;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.policy.AccessPointControllerImpl;
import com.android.systemui.statusbar.policy.BluetoothController;
import com.android.systemui.statusbar.policy.BluetoothControllerImpl;
import com.android.systemui.statusbar.policy.CastController;
@@ -45,8 +51,11 @@
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.policy.ZenModeControllerImpl;
+import java.util.concurrent.Executor;
+
import dagger.Binds;
import dagger.Module;
+import dagger.Provides;
/** Dagger Module for code in the statusbar.policy package. */
@@ -109,4 +118,27 @@
@Binds
ZenModeController provideZenModeController(ZenModeControllerImpl controllerImpl);
+ /** */
+ @Binds
+ NetworkController.AccessPointController provideAccessPointController(
+ AccessPointControllerImpl accessPointControllerImpl);
+
+ /** */
+ @SysUISingleton
+ @Provides
+ static AccessPointControllerImpl provideAccessPointControllerImpl(
+ UserManager userManager,
+ UserTracker userTracker,
+ @Main Executor mainExecutor,
+ AccessPointControllerImpl.WifiPickerTrackerFactory wifiPickerTrackerFactory
+ ) {
+ AccessPointControllerImpl controller = new AccessPointControllerImpl(
+ userManager,
+ userTracker,
+ mainExecutor,
+ wifiPickerTrackerFactory
+ );
+ controller.init();
+ return controller;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
index 0d63324..0ba072e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
+++ b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
@@ -25,7 +25,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
-import com.android.wm.shell.pip.tv.PipNotification;
+import com.android.wm.shell.pip.tv.TvPipNotificationController;
import java.util.Arrays;
@@ -36,7 +36,7 @@
public static String GENERAL = "GEN";
public static String STORAGE = "DSK";
public static String BATTERY = "BAT";
- public static String TVPIP = PipNotification.NOTIFICATION_CHANNEL_TVPIP;
+ public static String TVPIP = TvPipNotificationController.NOTIFICATION_CHANNEL; // "TVPIP"
public static String HINTS = "HNT";
public NotificationChannels(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
index 4d3af9c..8a79ace 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
@@ -32,9 +32,9 @@
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipUiEventLogger;
-import com.android.wm.shell.pip.tv.PipController;
-import com.android.wm.shell.pip.tv.PipNotification;
+import com.android.wm.shell.pip.tv.TvPipController;
import com.android.wm.shell.pip.tv.TvPipMenuController;
+import com.android.wm.shell.pip.tv.TvPipNotificationController;
import java.util.Optional;
@@ -55,31 +55,24 @@
PipTaskOrganizer pipTaskOrganizer,
TvPipMenuController tvPipMenuController,
PipMediaController pipMediaController,
- PipNotification pipNotification,
+ TvPipNotificationController tvPipNotificationController,
TaskStackListenerImpl taskStackListener,
WindowManagerShellWrapper windowManagerShellWrapper) {
return Optional.of(
- new PipController(
+ new TvPipController(
context,
pipBoundsState,
pipBoundsAlgorithm,
pipTaskOrganizer,
tvPipMenuController,
pipMediaController,
- pipNotification,
+ tvPipNotificationController,
taskStackListener,
windowManagerShellWrapper));
}
@WMSingleton
@Provides
- static PipNotification providePipNotification(Context context,
- PipMediaController pipMediaController) {
- return new PipNotification(context, pipMediaController);
- }
-
- @WMSingleton
- @Provides
static PipBoundsAlgorithm providePipBoundsHandler(Context context,
PipBoundsState pipBoundsState) {
return new PipBoundsAlgorithm(context, pipBoundsState);
@@ -93,7 +86,7 @@
@WMSingleton
@Provides
- static TvPipMenuController providesPipTvMenuController(
+ static TvPipMenuController providesTvPipMenuController(
Context context,
PipBoundsState pipBoundsState,
SystemWindows systemWindows,
@@ -103,15 +96,22 @@
@WMSingleton
@Provides
+ static TvPipNotificationController provideTvPipNotificationController(Context context,
+ PipMediaController pipMediaController) {
+ return new TvPipNotificationController(context, pipMediaController);
+ }
+
+ @WMSingleton
+ @Provides
static PipTaskOrganizer providePipTaskOrganizer(Context context,
- TvPipMenuController tvMenuController,
+ TvPipMenuController tvPipMenuController,
PipBoundsState pipBoundsState,
PipBoundsAlgorithm pipBoundsAlgorithm,
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
Optional<LegacySplitScreen> splitScreenOptional, DisplayController displayController,
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer) {
return new PipTaskOrganizer(context, pipBoundsState, pipBoundsAlgorithm,
- tvMenuController, pipSurfaceTransactionHelper, splitScreenOptional,
+ tvPipMenuController, pipSurfaceTransactionHelper, splitScreenOptional,
displayController, pipUiEventLogger, shellTaskOrganizer);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
index 1d3f26e7..f0ccc6d 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
@@ -21,7 +21,6 @@
import com.android.systemui.dagger.WMSingleton;
import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.Transitions;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.ShellExecutor;
@@ -32,6 +31,7 @@
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
+import com.android.wm.shell.transition.Transitions;
import dagger.Module;
import dagger.Provides;
@@ -45,9 +45,9 @@
@WMSingleton
@Provides
static DisplayImeController provideDisplayImeController(IWindowManager wmService,
- DisplayController displayController, @ShellMainThread ShellExecutor shellMainExecutor,
+ DisplayController displayController, @ShellMainThread ShellExecutor mainExecutor,
TransactionPool transactionPool) {
- return new DisplayImeController(wmService, displayController, shellMainExecutor,
+ return new DisplayImeController(wmService, displayController, mainExecutor,
transactionPool);
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 0819429..715b0a2 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -43,6 +43,7 @@
import com.android.systemui.Dependency;
import com.android.systemui.SystemUI;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationModeController;
@@ -67,6 +68,7 @@
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Optional;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -86,18 +88,21 @@
| SYSUI_STATE_BUBBLES_EXPANDED
| SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
+ // Shell interfaces
+ private final Optional<Pip> mPipOptional;
+ private final Optional<LegacySplitScreen> mSplitScreenOptional;
+ private final Optional<OneHanded> mOneHandedOptional;
+ private final Optional<HideDisplayCutout> mHideDisplayCutoutOptional;
+ private final Optional<ShellCommandHandler> mShellCommandHandler;
+
private final CommandQueue mCommandQueue;
private final ConfigurationController mConfigurationController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final NavigationModeController mNavigationModeController;
private final ScreenLifecycle mScreenLifecycle;
private final SysUiState mSysUiState;
- private final Optional<Pip> mPipOptional;
- private final Optional<LegacySplitScreen> mSplitScreenOptional;
- private final Optional<OneHanded> mOneHandedOptional;
- private final Optional<HideDisplayCutout> mHideDisplayCutoutOptional;
private final ProtoTracer mProtoTracer;
- private final Optional<ShellCommandHandler> mShellCommandHandler;
+ private final Executor mSysUiMainExecutor;
private boolean mIsSysUiStateValid;
private KeyguardUpdateMonitorCallback mSplitScreenKeyguardCallback;
@@ -105,18 +110,20 @@
private KeyguardUpdateMonitorCallback mOneHandedKeyguardCallback;
@Inject
- public WMShell(Context context, CommandQueue commandQueue,
+ public WMShell(Context context,
+ Optional<Pip> pipOptional,
+ Optional<LegacySplitScreen> splitScreenOptional,
+ Optional<OneHanded> oneHandedOptional,
+ Optional<HideDisplayCutout> hideDisplayCutoutOptional,
+ Optional<ShellCommandHandler> shellCommandHandler,
+ CommandQueue commandQueue,
ConfigurationController configurationController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
NavigationModeController navigationModeController,
ScreenLifecycle screenLifecycle,
SysUiState sysUiState,
- Optional<Pip> pipOptional,
- Optional<LegacySplitScreen> splitScreenOptional,
- Optional<OneHanded> oneHandedOptional,
- Optional<HideDisplayCutout> hideDisplayCutoutOptional,
ProtoTracer protoTracer,
- Optional<ShellCommandHandler> shellCommandHandler) {
+ @Main Executor sysUiMainExecutor) {
super(context);
mCommandQueue = commandQueue;
mConfigurationController = configurationController;
@@ -129,12 +136,13 @@
mOneHandedOptional = oneHandedOptional;
mHideDisplayCutoutOptional = hideDisplayCutoutOptional;
mProtoTracer = protoTracer;
- mProtoTracer.add(this);
mShellCommandHandler = shellCommandHandler;
+ mSysUiMainExecutor = sysUiMainExecutor;
}
@Override
public void start() {
+ mProtoTracer.add(this);
mCommandQueue.addCallback(this);
mPipOptional.ifPresent(this::initPip);
mSplitScreenOptional.ifPresent(this::initSplitScreen);
@@ -213,34 +221,43 @@
oneHanded.registerTransitionCallback(new OneHandedTransitionCallback() {
@Override
public void onStartFinished(Rect bounds) {
- mSysUiState.setFlag(SYSUI_STATE_ONE_HANDED_ACTIVE,
- true).commitUpdate(DEFAULT_DISPLAY);
+ mSysUiMainExecutor.execute(() -> {
+ mSysUiState.setFlag(SYSUI_STATE_ONE_HANDED_ACTIVE,
+ true).commitUpdate(DEFAULT_DISPLAY);
+ });
}
@Override
public void onStopFinished(Rect bounds) {
- mSysUiState.setFlag(SYSUI_STATE_ONE_HANDED_ACTIVE,
- false).commitUpdate(DEFAULT_DISPLAY);
+ mSysUiMainExecutor.execute(() -> {
+ mSysUiState.setFlag(SYSUI_STATE_ONE_HANDED_ACTIVE,
+ false).commitUpdate(DEFAULT_DISPLAY);
+ });
}
});
oneHanded.registerGestureCallback(new OneHandedGestureEventCallback() {
@Override
public void onStart() {
- if (oneHanded.isOneHandedEnabled()) {
- oneHanded.startOneHanded();
- } else if (oneHanded.isSwipeToNotificationEnabled()) {
- mCommandQueue.handleSystemKey(KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN);
- }
+ mSysUiMainExecutor.execute(() -> {
+ if (oneHanded.isOneHandedEnabled()) {
+ oneHanded.startOneHanded();
+ } else if (oneHanded.isSwipeToNotificationEnabled()) {
+ mCommandQueue.handleSystemKey(KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN);
+ }
+ });
}
@Override
public void onStop() {
- if (oneHanded.isOneHandedEnabled()) {
- oneHanded.stopOneHanded(OneHandedEvents.EVENT_ONE_HANDED_TRIGGER_GESTURE_OUT);
- } else if (oneHanded.isSwipeToNotificationEnabled()) {
- mCommandQueue.handleSystemKey(KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP);
- }
+ mSysUiMainExecutor.execute(() -> {
+ if (oneHanded.isOneHandedEnabled()) {
+ oneHanded.stopOneHanded(
+ OneHandedEvents.EVENT_ONE_HANDED_TRIGGER_GESTURE_OUT);
+ } else if (oneHanded.isSwipeToNotificationEnabled()) {
+ mCommandQueue.handleSystemKey(KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP);
+ }
+ });
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index b0bb3fd..bcb4386 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -42,7 +42,6 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.TaskViewFactory;
import com.android.wm.shell.TaskViewFactoryController;
-import com.android.wm.shell.Transitions;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.apppairs.AppPairs;
import com.android.wm.shell.bubbles.BubbleController;
@@ -71,6 +70,7 @@
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.phone.PipAppOpsListener;
import com.android.wm.shell.pip.phone.PipTouchHandler;
+import com.android.wm.shell.transition.Transitions;
import java.util.Optional;
@@ -110,9 +110,9 @@
@ShellMainThread
public static Handler provideShellMainHandler(@Main Handler sysuiMainHandler) {
if (ENABLE_SHELL_MAIN_THREAD) {
- HandlerThread shellMainThread = new HandlerThread("wmshell.main");
- shellMainThread.start();
- return shellMainThread.getThreadHandler();
+ HandlerThread mainThread = new HandlerThread("wmshell.main");
+ mainThread.start();
+ return mainThread.getThreadHandler();
}
return sysuiMainHandler;
}
@@ -123,10 +123,10 @@
@WMSingleton
@Provides
@ShellMainThread
- public static ShellExecutor provideShellMainExecutor(@ShellMainThread Handler shellMainHandler,
+ public static ShellExecutor provideShellMainExecutor(@ShellMainThread Handler mainHandler,
@Main ShellExecutor sysuiMainExecutor) {
if (ENABLE_SHELL_MAIN_THREAD) {
- return new HandlerExecutor(shellMainHandler);
+ return new HandlerExecutor(mainHandler);
}
return sysuiMainExecutor;
}
@@ -176,14 +176,16 @@
Optional<LegacySplitScreen> legacySplitScreenOptional,
Optional<AppPairs> appPairsOptional,
FullscreenTaskListener fullscreenTaskListener,
- @ShellMainThread ShellExecutor shellMainExecutor) {
+ Transitions transitions,
+ @ShellMainThread ShellExecutor mainExecutor) {
return ShellInitImpl.create(displayImeController,
dragAndDropController,
shellTaskOrganizer,
legacySplitScreenOptional,
appPairsOptional,
fullscreenTaskListener,
- shellMainExecutor);
+ transitions,
+ mainExecutor);
}
/**
@@ -199,10 +201,10 @@
Optional<OneHanded> oneHandedOptional,
Optional<HideDisplayCutout> hideDisplayCutout,
Optional<AppPairs> appPairsOptional,
- @ShellMainThread ShellExecutor shellMainExecutor) {
+ @ShellMainThread ShellExecutor mainExecutor) {
return Optional.of(ShellCommandHandlerImpl.create(shellTaskOrganizer,
legacySplitScreenOptional, pipOptional, oneHandedOptional, hideDisplayCutout,
- appPairsOptional, shellMainExecutor));
+ appPairsOptional, mainExecutor));
}
@WMSingleton
@@ -214,8 +216,8 @@
@WMSingleton
@Provides
static DisplayController provideDisplayController(Context context,
- IWindowManager wmService, @ShellMainThread ShellExecutor shellMainExecutor) {
- return new DisplayController(context, wmService, shellMainExecutor);
+ IWindowManager wmService, @ShellMainThread ShellExecutor mainExecutor) {
+ return new DisplayController(context, wmService, mainExecutor);
}
@WMSingleton
@@ -234,8 +236,8 @@
@WMSingleton
@Provides
static WindowManagerShellWrapper provideWindowManagerShellWrapper(
- @ShellMainThread ShellExecutor shellMainExecutor) {
- return new WindowManagerShellWrapper(shellMainExecutor);
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new WindowManagerShellWrapper(mainExecutor);
}
@WMSingleton
@@ -275,8 +277,8 @@
@WMSingleton
@Provides
static SyncTransactionQueue provideSyncTransactionQueue(TransactionPool pool,
- @ShellMainThread ShellExecutor shellMainExecutor) {
- return new SyncTransactionQueue(pool, shellMainExecutor);
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new SyncTransactionQueue(pool, mainExecutor);
}
@WMSingleton
@@ -297,8 +299,8 @@
@WMSingleton
@Provides
static TaskStackListenerImpl providerTaskStackListenerImpl(
- @ShellMainThread Handler shellMainHandler) {
- return new TaskStackListenerImpl(shellMainHandler);
+ @ShellMainThread Handler mainHandler) {
+ return new TaskStackListenerImpl(mainHandler);
}
@BindsOptionalOf
@@ -317,20 +319,22 @@
LauncherApps launcherApps,
UiEventLogger uiEventLogger,
ShellTaskOrganizer organizer,
- @ShellMainThread ShellExecutor shellMainExecutor) {
+ @ShellMainThread ShellExecutor mainExecutor) {
return Optional.of(BubbleController.create(context, null /* synchronizer */,
floatingContentCoordinator, statusBarService, windowManager,
windowManagerShellWrapper, launcherApps, uiEventLogger, organizer,
- shellMainExecutor));
+ mainExecutor));
}
+ // Needs the shell main handler for ContentObserver callbacks
@WMSingleton
@Provides
static Optional<OneHanded> provideOneHandedController(Context context,
DisplayController displayController, TaskStackListenerImpl taskStackListener,
- @ShellMainThread ShellExecutor mainExecutor) {
+ @ShellMainThread ShellExecutor mainExecutor,
+ @ShellMainThread Handler mainHandler) {
return Optional.ofNullable(OneHandedController.create(context, displayController,
- taskStackListener, mainExecutor));
+ taskStackListener, mainExecutor, mainHandler));
}
@WMSingleton
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
index 509419e..88f6e1f 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
@@ -17,13 +17,10 @@
package com.android.systemui.wmshell;
import android.content.Context;
-import android.os.Handler;
import android.view.IWindowManager;
import com.android.systemui.dagger.WMSingleton;
-import com.android.systemui.dagger.qualifiers.Main;
import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.Transitions;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.apppairs.AppPairs;
import com.android.wm.shell.apppairs.AppPairsController;
@@ -49,9 +46,9 @@
import com.android.wm.shell.pip.phone.PipAppOpsListener;
import com.android.wm.shell.pip.phone.PipController;
import com.android.wm.shell.pip.phone.PipTouchHandler;
+import com.android.wm.shell.transition.Transitions;
import java.util.Optional;
-import java.util.concurrent.Executor;
import dagger.Module;
import dagger.Provides;
@@ -65,9 +62,9 @@
@WMSingleton
@Provides
static DisplayImeController provideDisplayImeController(IWindowManager wmService,
- DisplayController displayController, @ShellMainThread ShellExecutor shellMainExecutor,
+ DisplayController displayController, @ShellMainThread ShellExecutor mainExecutor,
TransactionPool transactionPool) {
- return new DisplayImeController(wmService, displayController, shellMainExecutor,
+ return new DisplayImeController(wmService, displayController, mainExecutor,
transactionPool);
}
@@ -99,11 +96,11 @@
PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer,
PipTouchHandler pipTouchHandler, WindowManagerShellWrapper windowManagerShellWrapper,
TaskStackListenerImpl taskStackListener,
- @ShellMainThread ShellExecutor shellMainExecutor) {
+ @ShellMainThread ShellExecutor mainExecutor) {
return Optional.ofNullable(PipController.create(context, displayController,
pipAppOpsListener, pipBoundsAlgorithm, pipBoundsState, pipMediaController,
phonePipMenuController, pipTaskOrganizer, pipTouchHandler,
- windowManagerShellWrapper, taskStackListener, shellMainExecutor));
+ windowManagerShellWrapper, taskStackListener, mainExecutor));
}
@WMSingleton
@@ -134,10 +131,10 @@
PipTaskOrganizer pipTaskOrganizer,
FloatingContentCoordinator floatingContentCoordinator,
PipUiEventLogger pipUiEventLogger,
- @ShellMainThread ShellExecutor shellMainExecutor) {
+ @ShellMainThread ShellExecutor mainExecutor) {
return new PipTouchHandler(context, menuPhoneController, pipBoundsAlgorithm,
pipBoundsState, pipTaskOrganizer, floatingContentCoordinator, pipUiEventLogger,
- shellMainExecutor);
+ mainExecutor);
}
@WMSingleton
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index 6978ef4..4e4c33a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -37,13 +37,14 @@
import android.view.animation.AccelerateInterpolator;
import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.MediumTest;
+import androidx.test.filters.LargeTest;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.SysuiTestCase;
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -54,7 +55,8 @@
import java.util.concurrent.atomic.AtomicReference;
-@MediumTest
+@Ignore
+@LargeTest
@RunWith(AndroidTestingRunner.class)
public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/AccessPointControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/AccessPointControllerImplTest.kt
new file mode 100644
index 0000000..4068f93
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/AccessPointControllerImplTest.kt
@@ -0,0 +1,229 @@
+/*
+ * 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.policy
+
+import android.os.UserManager
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.lifecycle.Lifecycle
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.mockito.capture
+import com.android.wifitrackerlib.WifiEntry
+import com.android.wifitrackerlib.WifiPickerTracker
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyList
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import java.util.concurrent.Executor
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+class AccessPointControllerImplTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var userManager: UserManager
+ @Mock
+ private lateinit var userTracker: UserTracker
+ @Mock
+ private lateinit var wifiPickerTrackerFactory:
+ AccessPointControllerImpl.WifiPickerTrackerFactory
+ @Mock
+ private lateinit var wifiPickerTracker: WifiPickerTracker
+ @Mock
+ private lateinit var callback: NetworkController.AccessPointController.AccessPointCallback
+ @Mock
+ private lateinit var otherCallback: NetworkController.AccessPointController.AccessPointCallback
+ @Mock
+ private lateinit var wifiEntryConnected: WifiEntry
+ @Mock
+ private lateinit var wifiEntryOther: WifiEntry
+ @Captor
+ private lateinit var wifiEntryListCaptor: ArgumentCaptor<List<WifiEntry>>
+
+ private val instantExecutor = Executor { it.run() }
+ private lateinit var controller: AccessPointControllerImpl
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ `when`(wifiPickerTrackerFactory.create(any(), any())).thenReturn(wifiPickerTracker)
+
+ `when`(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntryConnected)
+ `when`(wifiPickerTracker.wifiEntries).thenReturn(ArrayList<WifiEntry>().apply {
+ add(wifiEntryOther)
+ })
+
+ controller = AccessPointControllerImpl(
+ userManager,
+ userTracker,
+ instantExecutor,
+ wifiPickerTrackerFactory
+ )
+
+ controller.init()
+ }
+
+ @Test
+ fun testInitialLifecycleStateCreated() {
+ assertThat(controller.lifecycle.currentState).isEqualTo(Lifecycle.State.CREATED)
+ }
+
+ @Test
+ fun testLifecycleStartedAfterFirstCallback() {
+ controller.addAccessPointCallback(callback)
+ assertThat(controller.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
+ }
+
+ @Test
+ fun testLifecycleBackToCreatedAfterRemovingOnlyCallback() {
+ controller.addAccessPointCallback(callback)
+ controller.removeAccessPointCallback(callback)
+
+ assertThat(controller.lifecycle.currentState).isEqualTo(Lifecycle.State.CREATED)
+ }
+
+ @Test
+ fun testLifecycleStillStartedAfterRemovingSecondCallback() {
+ controller.addAccessPointCallback(callback)
+ controller.addAccessPointCallback(otherCallback)
+ controller.removeAccessPointCallback(callback)
+
+ assertThat(controller.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
+ }
+
+ @Test
+ fun testScanForAccessPointsTriggersCallbackWithEntriesInOrder() {
+ controller.addAccessPointCallback(callback)
+ controller.scanForAccessPoints()
+
+ verify(callback).onAccessPointsChanged(capture(wifiEntryListCaptor))
+
+ assertThat(wifiEntryListCaptor.value).containsExactly(wifiEntryConnected, wifiEntryOther)
+ }
+
+ @Test
+ fun testOnWifiStateChangedTriggersCallbackWithEntriesInOrder() {
+ controller.addAccessPointCallback(callback)
+ controller.onWifiStateChanged()
+
+ verify(callback).onAccessPointsChanged(capture(wifiEntryListCaptor))
+
+ assertThat(wifiEntryListCaptor.value).containsExactly(wifiEntryConnected, wifiEntryOther)
+ }
+
+ @Test
+ fun testOnWifiEntriesChangedTriggersCallbackWithEntriesInOrder() {
+ controller.addAccessPointCallback(callback)
+ controller.onWifiEntriesChanged()
+
+ verify(callback).onAccessPointsChanged(capture(wifiEntryListCaptor))
+
+ assertThat(wifiEntryListCaptor.value).containsExactly(wifiEntryConnected, wifiEntryOther)
+ }
+
+ @Test
+ fun testOnNumSavedNetworksChangedDoesntTriggerCallback() {
+ controller.addAccessPointCallback(callback)
+ controller.onNumSavedNetworksChanged()
+
+ verify(callback, never()).onAccessPointsChanged(anyList())
+ }
+
+ @Test
+ fun testOnNumSavedSubscriptionsChangedDoesntTriggerCallback() {
+ controller.addAccessPointCallback(callback)
+ controller.onNumSavedSubscriptionsChanged()
+
+ verify(callback, never()).onAccessPointsChanged(anyList())
+ }
+
+ @Test
+ fun testReturnEmptyListWhenNoWifiPickerTracker() {
+ `when`(wifiPickerTrackerFactory.create(any(), any())).thenReturn(null)
+ val otherController = AccessPointControllerImpl(
+ userManager,
+ userTracker,
+ instantExecutor,
+ wifiPickerTrackerFactory
+ )
+ otherController.init()
+
+ otherController.addAccessPointCallback(callback)
+ otherController.scanForAccessPoints()
+
+ verify(callback).onAccessPointsChanged(capture(wifiEntryListCaptor))
+
+ assertThat(wifiEntryListCaptor.value).isEmpty()
+ }
+
+ @Test
+ fun connectToNullEntry() {
+ controller.addAccessPointCallback(callback)
+
+ assertThat(controller.connect(null)).isFalse()
+
+ verify(callback, never()).onSettingsActivityTriggered(any())
+ }
+
+ @Test
+ fun connectToSavedWifiEntry() {
+ controller.addAccessPointCallback(callback)
+ `when`(wifiEntryOther.isSaved).thenReturn(true)
+
+ assertThat(controller.connect(wifiEntryOther)).isFalse()
+
+ verify(wifiEntryOther).connect(any())
+ verify(callback, never()).onSettingsActivityTriggered(any())
+ }
+
+ @Test
+ fun connectToSecuredNotSavedWifiEntry() {
+ controller.addAccessPointCallback(callback)
+ `when`(wifiEntryOther.isSaved).thenReturn(false)
+ `when`(wifiEntryOther.security).thenReturn(WifiEntry.SECURITY_EAP)
+
+ // True means we will launch WifiSettings
+ assertThat(controller.connect(wifiEntryOther)).isTrue()
+
+ verify(wifiEntryOther, never()).connect(any())
+ verify(callback).onSettingsActivityTriggered(any())
+ }
+
+ @Test
+ fun connectToNotSecuredNotSavedWifiEntry() {
+ controller.addAccessPointCallback(callback)
+ `when`(wifiEntryOther.isSaved).thenReturn(false)
+ `when`(wifiEntryOther.security).thenReturn(WifiEntry.SECURITY_NONE)
+
+ assertThat(controller.connect(wifiEntryOther)).isFalse()
+
+ verify(wifiEntryOther).connect(any())
+ verify(callback, never()).onSettingsActivityTriggered(any())
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index 31bf7120..446d3f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -34,6 +34,7 @@
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.tracing.ProtoTracer;
import com.android.wm.shell.ShellCommandHandler;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.onehanded.OneHanded;
@@ -63,22 +64,22 @@
@Mock SysUiState mSysUiState;
@Mock Pip mPip;
@Mock PipTouchHandler mPipTouchHandler;
- @Mock
- LegacySplitScreen mLegacySplitScreen;
+ @Mock LegacySplitScreen mLegacySplitScreen;
@Mock OneHanded mOneHanded;
@Mock HideDisplayCutout mHideDisplayCutout;
@Mock ProtoTracer mProtoTracer;
@Mock ShellCommandHandler mShellCommandHandler;
+ @Mock ShellExecutor mSysUiMainExecutor;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mWMShell = new WMShell(mContext, mCommandQueue, mConfigurationController,
+ mWMShell = new WMShell(mContext, Optional.of(mPip), Optional.of(mLegacySplitScreen),
+ Optional.of(mOneHanded), Optional.of(mHideDisplayCutout),
+ Optional.of(mShellCommandHandler), mCommandQueue, mConfigurationController,
mKeyguardUpdateMonitor, mNavigationModeController,
- mScreenLifecycle, mSysUiState, Optional.of(mPip), Optional.of(mLegacySplitScreen),
- Optional.of(mOneHanded), Optional.of(mHideDisplayCutout), mProtoTracer,
- Optional.of(mShellCommandHandler));
+ mScreenLifecycle, mSysUiState, mProtoTracer, mSysUiMainExecutor);
when(mPip.getPipTouchHandler()).thenReturn(mPipTouchHandler);
}
diff --git a/services/Android.bp b/services/Android.bp
index 3447b5c..da24719 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -30,6 +30,7 @@
":services.searchui-sources",
":services.startop.iorap-sources",
":services.systemcaptions-sources",
+ ":services.translation-sources",
":services.usage-sources",
":services.usb-sources",
":services.voiceinteraction-sources",
@@ -76,12 +77,12 @@
"services.searchui",
"services.startop",
"services.systemcaptions",
+ "services.translation",
"services.usage",
"services.usb",
"services.voiceinteraction",
"services.wifi",
"service-blobstore",
- "service-connectivity",
"service-jobscheduler",
"android.hidl.base-V1.0-java",
],
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 9d02835..728e829 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -33,6 +33,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
+import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.app.role.RoleManager;
@@ -53,6 +54,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
import android.net.NetworkPolicyManager;
import android.os.Binder;
@@ -111,7 +113,6 @@
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -169,6 +170,10 @@
@GuardedBy("mLock")
private @Nullable SparseArray<Set<Association>> mCachedAssociations = new SparseArray<>();
+ ActivityTaskManagerInternal mAtmInternal;
+ ActivityManagerInternal mAmInternal;
+ PackageManagerInternal mPackageManagerInternal;
+
public CompanionDeviceManagerService(Context context) {
super(context);
mImpl = new CompanionDeviceManagerImpl();
@@ -176,6 +181,9 @@
mRoleManager = context.getSystemService(RoleManager.class);
mAppOpsManager = IAppOpsService.Stub.asInterface(
ServiceManager.getService(Context.APP_OPS_SERVICE));
+ mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
+ mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
+ mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
Intent serviceIntent = new Intent().setComponent(SERVICE_TO_BIND_TO);
mServiceConnectors = new PerUser<ServiceConnector<ICompanionDeviceDiscoveryService>>() {
@@ -236,15 +244,7 @@
if (associations == null || associations.isEmpty()) {
return;
}
- Set<String> companionAppPackages = new HashSet<>();
- for (Association association : associations) {
- companionAppPackages.add(association.getPackageName());
- }
- ActivityTaskManagerInternal atmInternal = LocalServices.getService(
- ActivityTaskManagerInternal.class);
- if (atmInternal != null) {
- atmInternal.setCompanionAppPackages(userHandle, companionAppPackages);
- }
+ updateAtm(userHandle, associations);
BackgroundThread.getHandler().sendMessageDelayed(
obtainMessage(CompanionDeviceManagerService::maybeGrantAutoRevokeExemptions, this),
@@ -727,12 +727,6 @@
final Set<Association> old = getAllAssociations(userId);
Set<Association> associations = new ArraySet<>(old);
associations = update.apply(associations);
-
- Set<String> companionAppPackages = new HashSet<>();
- for (Association association : associations) {
- companionAppPackages.add(association.getPackageName());
- }
-
if (DEBUG) {
Slog.i(LOG_TAG, "Updating associations: " + old + " --> " + associations);
}
@@ -741,9 +735,25 @@
CompanionDeviceManagerService::persistAssociations,
this, associations, userId));
- ActivityTaskManagerInternal atmInternal = LocalServices.getService(
- ActivityTaskManagerInternal.class);
- atmInternal.setCompanionAppPackages(userId, companionAppPackages);
+ updateAtm(userId, associations);
+ }
+ }
+
+ private void updateAtm(int userId, Set<Association> associations) {
+ final Set<Integer> companionAppUids = new ArraySet<>();
+ for (Association association : associations) {
+ final int uid = mPackageManagerInternal.getPackageUid(association.getPackageName(),
+ 0, userId);
+ if (uid >= 0) {
+ companionAppUids.add(uid);
+ }
+ }
+ if (mAtmInternal != null) {
+ mAtmInternal.setCompanionAppUids(userId, companionAppUids);
+ }
+ if (mAmInternal != null) {
+ // Make a copy of companionAppUids and send it to ActivityManager.
+ mAmInternal.setCompanionAppUids(userId, new ArraySet<>(companionAppUids));
}
}
diff --git a/services/core/java/android/power/OWNERS b/services/core/java/android/power/OWNERS
new file mode 100644
index 0000000..4068e2b
--- /dev/null
+++ b/services/core/java/android/power/OWNERS
@@ -0,0 +1 @@
+include /BATTERY_STATS_OWNERS
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 397eeb2..f1f1c8e 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -56,12 +56,14 @@
import static android.net.NetworkPolicyManager.uidRulesToString;
import static android.net.shared.NetworkMonitorUtils.isPrivateDnsValidationRequired;
import static android.os.Process.INVALID_UID;
+import static android.os.Process.VPN_UID;
import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.IPPROTO_UDP;
import static java.util.Map.Entry;
import android.Manifest;
+import android.annotation.BoolRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
@@ -620,7 +622,7 @@
private LingerMonitor mLingerMonitor;
// sequence number of NetworkRequests
- private int mNextNetworkRequestId = 1;
+ private int mNextNetworkRequestId = NetworkRequest.FIRST_REQUEST_ID;
// Sequence number for NetworkProvider IDs.
private final AtomicInteger mNextNetworkProviderId = new AtomicInteger(
@@ -973,6 +975,10 @@
mDefaultWifiRequest = createDefaultInternetRequestForTransport(
NetworkCapabilities.TRANSPORT_WIFI, NetworkRequest.Type.BACKGROUND_REQUEST);
+ mDefaultVehicleRequest = createAlwaysOnRequestForCapability(
+ NetworkCapabilities.NET_CAPABILITY_VEHICLE_INTERNAL,
+ NetworkRequest.Type.BACKGROUND_REQUEST);
+
mHandlerThread = mDeps.makeHandlerThread();
mHandlerThread.start();
mHandler = new InternalHandler(mHandlerThread.getLooper());
@@ -1172,6 +1178,15 @@
return new NetworkRequest(netCap, TYPE_NONE, nextNetworkRequestId(), type);
}
+ private NetworkRequest createAlwaysOnRequestForCapability(int capability,
+ NetworkRequest.Type type) {
+ final NetworkCapabilities netCap = new NetworkCapabilities();
+ netCap.clearAll();
+ netCap.addCapability(capability);
+ netCap.setRequestorUidAndPackageName(Process.myUid(), mContext.getPackageName());
+ return new NetworkRequest(netCap, TYPE_NONE, nextNetworkRequestId(), type);
+ }
+
// Used only for testing.
// TODO: Delete this and either:
// 1. Give FakeSettingsProvider the ability to send settings change notifications (requires
@@ -1189,10 +1204,19 @@
mHandler.sendEmptyMessage(EVENT_PRIVATE_DNS_SETTINGS_CHANGED);
}
+ private void handleAlwaysOnNetworkRequest(NetworkRequest networkRequest, @BoolRes int id) {
+ final boolean enable = mContext.getResources().getBoolean(id);
+ handleAlwaysOnNetworkRequest(networkRequest, enable);
+ }
+
private void handleAlwaysOnNetworkRequest(
NetworkRequest networkRequest, String settingName, boolean defaultValue) {
final boolean enable = toBool(Settings.Global.getInt(
mContext.getContentResolver(), settingName, encodeBool(defaultValue)));
+ handleAlwaysOnNetworkRequest(networkRequest, enable);
+ }
+
+ private void handleAlwaysOnNetworkRequest(NetworkRequest networkRequest, boolean enable) {
final boolean isEnabled = (mNetworkRequests.get(networkRequest) != null);
if (enable == isEnabled) {
return; // Nothing to do.
@@ -1209,9 +1233,11 @@
private void handleConfigureAlwaysOnNetworks() {
handleAlwaysOnNetworkRequest(
- mDefaultMobileDataRequest,Settings.Global.MOBILE_DATA_ALWAYS_ON, true);
+ mDefaultMobileDataRequest, Settings.Global.MOBILE_DATA_ALWAYS_ON, true);
handleAlwaysOnNetworkRequest(mDefaultWifiRequest, Settings.Global.WIFI_ALWAYS_REQUESTED,
false);
+ handleAlwaysOnNetworkRequest(mDefaultVehicleRequest,
+ com.android.internal.R.bool.config_vehicleInternalNetworkAlwaysRequested);
}
private void registerSettingsCallbacks() {
@@ -1238,6 +1264,8 @@
}
private synchronized int nextNetworkRequestId() {
+ // TODO: Consider handle wrapping and exclude {@link NetworkRequest#REQUEST_ID_NONE} if
+ // doing that.
return mNextNetworkRequestId++;
}
@@ -1329,15 +1357,20 @@
/**
* Check if UID should be blocked from using the specified network.
*/
- private boolean isNetworkWithLinkPropertiesBlocked(LinkProperties lp, int uid,
- boolean ignoreBlocked) {
+ private boolean isNetworkWithCapabilitiesBlocked(@Nullable final NetworkCapabilities nc,
+ final int uid, final boolean ignoreBlocked) {
// Networks aren't blocked when ignoring blocked status
if (ignoreBlocked) {
return false;
}
if (isUidBlockedByVpn(uid, mVpnBlockedUidRanges)) return true;
- final String iface = (lp == null ? "" : lp.getInterfaceName());
- return mPolicyManagerInternal.isUidNetworkingBlocked(uid, iface);
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ final boolean metered = nc == null ? true : nc.isMetered();
+ return mPolicyManager.isUidNetworkingBlocked(uid, metered);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
}
private void maybeLogBlockedNetworkInfo(NetworkInfo ni, int uid) {
@@ -1375,12 +1408,13 @@
/**
* Apply any relevant filters to {@link NetworkState} for the given UID. For
* example, this may mark the network as {@link DetailedState#BLOCKED} based
- * on {@link #isNetworkWithLinkPropertiesBlocked}.
+ * on {@link #isNetworkWithCapabilitiesBlocked}.
*/
private void filterNetworkStateForUid(NetworkState state, int uid, boolean ignoreBlocked) {
if (state == null || state.networkInfo == null || state.linkProperties == null) return;
- if (isNetworkWithLinkPropertiesBlocked(state.linkProperties, uid, ignoreBlocked)) {
+ if (isNetworkWithCapabilitiesBlocked(state.networkCapabilities, uid,
+ ignoreBlocked)) {
state.networkInfo.setDetailedState(DetailedState.BLOCKED, null, null);
}
synchronized (mVpns) {
@@ -1440,8 +1474,8 @@
}
}
nai = getDefaultNetwork();
- if (nai != null
- && isNetworkWithLinkPropertiesBlocked(nai.linkProperties, uid, ignoreBlocked)) {
+ if (nai != null && isNetworkWithCapabilitiesBlocked(
+ nai.networkCapabilities, uid, ignoreBlocked)) {
nai = null;
}
return nai != null ? nai.network : null;
@@ -1513,7 +1547,7 @@
enforceAccessPermission();
final int uid = mDeps.getCallingUid();
NetworkState state = getFilteredNetworkState(networkType, uid);
- if (!isNetworkWithLinkPropertiesBlocked(state.linkProperties, uid, false)) {
+ if (!isNetworkWithCapabilitiesBlocked(state.networkCapabilities, uid, false)) {
return state.network;
}
return null;
@@ -4471,7 +4505,8 @@
if (!nai.everConnected) {
return;
}
- if (isNetworkWithLinkPropertiesBlocked(nai.linkProperties, uid, false)) {
+ final NetworkCapabilities nc = getNetworkCapabilitiesInternal(nai);
+ if (isNetworkWithCapabilitiesBlocked(nc, uid, false)) {
return;
}
nai.networkMonitor().forceReevaluation(uid);
@@ -5956,6 +5991,9 @@
// priority networks like ethernet are active.
private final NetworkRequest mDefaultWifiRequest;
+ // Request used to optionally keep vehicle internal network always active
+ private final NetworkRequest mDefaultVehicleRequest;
+
private NetworkAgentInfo getDefaultNetwork() {
return mDefaultNetworkNai;
}
@@ -6666,6 +6704,39 @@
return stableRanges;
}
+ private void maybeCloseSockets(NetworkAgentInfo nai, UidRangeParcel[] ranges,
+ int[] exemptUids) {
+ if (nai.isVPN() && !nai.networkAgentConfig.allowBypass) {
+ try {
+ mNetd.socketDestroy(ranges, exemptUids);
+ } catch (Exception e) {
+ loge("Exception in socket destroy: ", e);
+ }
+ }
+ }
+
+ private void updateUidRanges(boolean add, NetworkAgentInfo nai, Set<UidRange> uidRanges) {
+ int[] exemptUids = new int[2];
+ // TODO: Excluding VPN_UID is necessary in order to not to kill the TCP connection used
+ // by PPTP. Fix this by making Vpn set the owner UID to VPN_UID instead of system when
+ // starting a legacy VPN, and remove VPN_UID here. (b/176542831)
+ exemptUids[0] = VPN_UID;
+ exemptUids[1] = nai.networkCapabilities.getOwnerUid();
+ UidRangeParcel[] ranges = toUidRangeStableParcels(uidRanges);
+
+ maybeCloseSockets(nai, ranges, exemptUids);
+ try {
+ if (add) {
+ mNetd.networkAddUidRanges(nai.network.netId, ranges);
+ } else {
+ mNetd.networkRemoveUidRanges(nai.network.netId, ranges);
+ }
+ } catch (Exception e) {
+ loge("Exception while " + (add ? "adding" : "removing") + " uid ranges " + uidRanges +
+ " on netId " + nai.network.netId + ". " + e);
+ }
+ maybeCloseSockets(nai, ranges, exemptUids);
+ }
private void updateUids(NetworkAgentInfo nai, NetworkCapabilities prevNc,
NetworkCapabilities newNc) {
@@ -6685,12 +6756,21 @@
// in both ranges are not subject to any VPN routing rules. Adding new range before
// removing old range works because, unlike the filtering rules below, it's possible to
// add duplicate UID routing rules.
+ // TODO: calculate the intersection of add & remove. Imagining that we are trying to
+ // remove uid 3 from a set containing 1-5. Intersection of the prev and new sets is:
+ // [1-5] & [1-2],[4-5] == [3]
+ // Then we can do:
+ // maybeCloseSockets([3])
+ // mNetd.networkAddUidRanges([1-2],[4-5])
+ // mNetd.networkRemoveUidRanges([1-5])
+ // maybeCloseSockets([3])
+ // This can prevent the sockets of uid 1-2, 4-5 from being closed. It also reduce the
+ // number of binder calls from 6 to 4.
if (!newRanges.isEmpty()) {
- mNetd.networkAddUidRanges(nai.network.netId, toUidRangeStableParcels(newRanges));
+ updateUidRanges(true, nai, newRanges);
}
if (!prevRanges.isEmpty()) {
- mNetd.networkRemoveUidRanges(
- nai.network.netId, toUidRangeStableParcels(prevRanges));
+ updateUidRanges(false, nai, prevRanges);
}
final boolean wasFiltering = requiresVpnIsolation(nai, prevNc, nai.linkProperties);
final boolean shouldFilter = requiresVpnIsolation(nai, newNc, nai.linkProperties);
@@ -7065,11 +7145,11 @@
log(" accepting network in place of " + previousSatisfier.toShortString());
}
previousSatisfier.removeRequest(nri.request.requestId);
- previousSatisfier.lingerRequest(nri.request, now, mLingerDelayMs);
+ previousSatisfier.lingerRequest(nri.request.requestId, now, mLingerDelayMs);
} else {
if (VDBG || DDBG) log(" accepting network in place of null");
}
- newSatisfier.unlingerRequest(nri.request);
+ newSatisfier.unlingerRequest(nri.request.requestId);
if (!newSatisfier.addRequest(nri.request)) {
Log.wtf(TAG, "BUG: " + newSatisfier.toShortString() + " already has "
+ nri.request);
diff --git a/services/core/java/com/android/server/ConnectivityServiceInitializer.java b/services/core/java/com/android/server/ConnectivityServiceInitializer.java
index f701688..0779f71 100644
--- a/services/core/java/com/android/server/ConnectivityServiceInitializer.java
+++ b/services/core/java/com/android/server/ConnectivityServiceInitializer.java
@@ -35,6 +35,8 @@
public ConnectivityServiceInitializer(Context context) {
super(context);
+ // Load JNI libraries used by ConnectivityService and its dependencies
+ System.loadLibrary("service-connectivity");
// TODO: Define formal APIs to get the needed services.
mConnectivity = new ConnectivityService(context, getNetworkManagementService(),
getNetworkStatsService());
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 99a1d86..8b506ba 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -54,9 +54,13 @@
import org.xmlpull.v1.XmlPullParserException;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Retention;
@@ -149,6 +153,11 @@
private static final String ATTR_PASSED_HEALTH_CHECK = "passed-health-check";
private static final String ATTR_MITIGATION_CALLS = "mitigation-calls";
+ // A file containing information about the current mitigation count in the case of a boot loop.
+ // This allows boot loop information to persist in the case of an fs-checkpoint being
+ // aborted.
+ private static final String METADATA_FILE = "/metadata/watchdog/mitigation_count.txt";
+
@GuardedBy("PackageWatchdog.class")
private static PackageWatchdog sPackageWatchdog;
@@ -492,6 +501,7 @@
}
if (currentObserverToNotify != null) {
mBootThreshold.setMitigationCount(mitigationCount);
+ mBootThreshold.saveMitigationCountToMetadata();
currentObserverToNotify.executeBootLoopMitigation(mitigationCount);
}
}
@@ -1700,9 +1710,31 @@
SystemProperties.set(property, Long.toString(newStart));
}
+ public void saveMitigationCountToMetadata() {
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(METADATA_FILE))) {
+ writer.write(String.valueOf(getMitigationCount()));
+ } catch (Exception e) {
+ Slog.e(TAG, "Could not save metadata to file: " + e);
+ }
+ }
+
+ public void readMitigationCountFromMetadataIfNecessary() {
+ File bootPropsFile = new File(METADATA_FILE);
+ if (bootPropsFile.exists()) {
+ try (BufferedReader reader = new BufferedReader(new FileReader(METADATA_FILE))) {
+ String mitigationCount = reader.readLine();
+ setMitigationCount(Integer.parseInt(mitigationCount));
+ bootPropsFile.delete();
+ } catch (Exception e) {
+ Slog.i(TAG, "Could not read metadata file: " + e);
+ }
+ }
+ }
+
/** Increments the boot counter, and returns whether the device is bootlooping. */
public boolean incrementAndTest() {
+ readMitigationCountFromMetadataIfNecessary();
final long now = mSystemClock.uptimeMillis();
if (now - getStart() < 0) {
Slog.e(TAG, "Window was less than zero. Resetting start to current time.");
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index a1cf816..db36e62 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -31,6 +31,7 @@
import android.os.Bundle;
import android.os.Environment;
import android.os.FileUtils;
+import android.os.PowerManager;
import android.os.RecoverySystem;
import android.os.RemoteCallback;
import android.os.SystemClock;
@@ -77,6 +78,7 @@
@VisibleForTesting
static final String PROP_ENABLE_RESCUE = "persist.sys.enable_rescue";
static final String PROP_ATTEMPTING_FACTORY_RESET = "sys.attempting_factory_reset";
+ static final String PROP_ATTEMPTING_REBOOT = "sys.attempting_reboot";
static final String PROP_MAX_RESCUE_LEVEL_ATTEMPTED = "sys.max_rescue_level_attempted";
@VisibleForTesting
static final int LEVEL_NONE = 0;
@@ -87,7 +89,9 @@
@VisibleForTesting
static final int LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS = 3;
@VisibleForTesting
- static final int LEVEL_FACTORY_RESET = 4;
+ static final int LEVEL_WARM_REBOOT = 4;
+ @VisibleForTesting
+ static final int LEVEL_FACTORY_RESET = 5;
@VisibleForTesting
static final String PROP_RESCUE_BOOT_COUNT = "sys.rescue_boot_count";
@VisibleForTesting
@@ -159,12 +163,24 @@
}
/**
- * Check if we're currently attempting to reboot for a factory reset.
+ * Check if we're currently attempting to reboot for a factory reset. This method must
+ * return true if RescueParty tries to reboot early during a boot loop, since the device
+ * will not be fully booted at this time.
+ *
+ * TODO(gavincorkery): Rename method since its scope has expanded.
*/
public static boolean isAttemptingFactoryReset() {
+ return isFactoryResetPropertySet() || isRebootPropertySet();
+ }
+
+ static boolean isFactoryResetPropertySet() {
return SystemProperties.getBoolean(PROP_ATTEMPTING_FACTORY_RESET, false);
}
+ static boolean isRebootPropertySet() {
+ return SystemProperties.getBoolean(PROP_ATTEMPTING_REBOOT, false);
+ }
+
/**
* Called when {@code SettingsProvider} has been published, which is a good
* opportunity to reset any settings depending on our rescue level.
@@ -329,8 +345,10 @@
return LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES;
} else if (mitigationCount == 3) {
return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS;
- } else if (mitigationCount >= 4) {
- return getMaxRescueLevel();
+ } else if (mitigationCount == 4) {
+ return Math.min(getMaxRescueLevel(), LEVEL_WARM_REBOOT);
+ } else if (mitigationCount >= 5) {
+ return Math.min(getMaxRescueLevel(), LEVEL_FACTORY_RESET);
} else {
Slog.w(TAG, "Expected positive mitigation count, was " + mitigationCount);
return LEVEL_NONE;
@@ -356,6 +374,8 @@
// Try our best to reset all settings possible, and once finished
// rethrow any exception that we encountered
Exception res = null;
+ Runnable runnable;
+ Thread thread;
switch (level) {
case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
try {
@@ -396,11 +416,26 @@
res = e;
}
break;
- case LEVEL_FACTORY_RESET:
+ case LEVEL_WARM_REBOOT:
// Request the reboot from a separate thread to avoid deadlock on PackageWatchdog
// when device shutting down.
+ SystemProperties.set(PROP_ATTEMPTING_REBOOT, "true");
+ runnable = () -> {
+ try {
+ PowerManager pm = context.getSystemService(PowerManager.class);
+ if (pm != null) {
+ pm.reboot(TAG);
+ }
+ } catch (Throwable t) {
+ logRescueException(level, t);
+ }
+ };
+ thread = new Thread(runnable);
+ thread.start();
+ break;
+ case LEVEL_FACTORY_RESET:
SystemProperties.set(PROP_ATTEMPTING_FACTORY_RESET, "true");
- Runnable runnable = new Runnable() {
+ runnable = new Runnable() {
@Override
public void run() {
try {
@@ -410,7 +445,7 @@
}
}
};
- Thread thread = new Thread(runnable);
+ thread = new Thread(runnable);
thread.start();
break;
}
@@ -433,6 +468,7 @@
case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
return PackageHealthObserverImpact.USER_IMPACT_LOW;
case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
+ case LEVEL_WARM_REBOOT:
case LEVEL_FACTORY_RESET:
return PackageHealthObserverImpact.USER_IMPACT_HIGH;
default:
@@ -714,6 +750,7 @@
case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS: return "RESET_SETTINGS_UNTRUSTED_DEFAULTS";
case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES: return "RESET_SETTINGS_UNTRUSTED_CHANGES";
case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS: return "RESET_SETTINGS_TRUSTED_DEFAULTS";
+ case LEVEL_WARM_REBOOT: return "WARM_REBOOT";
case LEVEL_FACTORY_RESET: return "FACTORY_RESET";
default: return Integer.toString(level);
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index b0f2e24..c951fd4 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1716,7 +1716,7 @@
public StorageManagerService(Context context) {
sSelf = this;
mVoldAppDataIsolationEnabled = SystemProperties.getBoolean(
- ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, true);
+ ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, false);
mContext = context;
mResolver = mContext.getContentResolver();
mCallbacks = new Callbacks(FgThread.get().getLooper());
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index bb07ee6..7af328a 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -170,6 +170,7 @@
public static final int FGS_FEATURE_ALLOWED_BY_PROCESS_RECORD = 19;
public static final int FGS_FEATURE_ALLOWED_BY_EXEMPTED_PACKAGES = 20;
public static final int FGS_FEATURE_ALLOWED_BY_ACTIVITY_STARTER = 21;
+ public static final int FGS_FEATURE_ALLOWED_BY_COMPANION_APP = 22;
@IntDef(flag = true, prefix = { "FGS_FEATURE_" }, value = {
FGS_FEATURE_DENIED,
@@ -192,7 +193,8 @@
FGS_FEATURE_ALLOWED_BY_DEVICE_DEMO_MODE,
FGS_FEATURE_ALLOWED_BY_PROCESS_RECORD,
FGS_FEATURE_ALLOWED_BY_EXEMPTED_PACKAGES,
- FGS_FEATURE_ALLOWED_BY_ACTIVITY_STARTER
+ FGS_FEATURE_ALLOWED_BY_ACTIVITY_STARTER,
+ FGS_FEATURE_ALLOWED_BY_COMPANION_APP
})
@Retention(RetentionPolicy.SOURCE)
public @interface FgsFeatureRetCode {}
@@ -5379,6 +5381,14 @@
}
}
+ if (ret == FGS_FEATURE_DENIED) {
+ final boolean isCompanionApp = mAm.mInternal.isAssociatedCompanionApp(
+ UserHandle.getUserId(callingUid), callingUid);
+ if (isCompanionApp) {
+ ret = FGS_FEATURE_ALLOWED_BY_COMPANION_APP;
+ }
+ }
+
final String debugInfo =
"[callingPackage: " + callingPackage
+ "; callingUid: " + callingUid
@@ -5462,6 +5472,8 @@
return "FGS_FEATURE_ALLOWED_BY_EXEMPTED_PACKAGES";
case FGS_FEATURE_ALLOWED_BY_ACTIVITY_STARTER:
return "ALLOWED_BY_ACTIVITY_STARTER";
+ case FGS_FEATURE_ALLOWED_BY_COMPANION_APP:
+ return "ALLOWED_BY_COMPANION_APP";
default:
return "";
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c1ab5b6..9e62e63 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -174,7 +174,6 @@
import android.app.WaitResult;
import android.app.backup.BackupManager.OperationType;
import android.app.backup.IBackupManager;
-import android.app.compat.CompatChanges;
import android.app.usage.UsageEvents;
import android.app.usage.UsageEvents.Event;
import android.app.usage.UsageStatsManager;
@@ -573,6 +572,9 @@
private int mDeviceOwnerUid = Process.INVALID_UID;
+ // A map userId and all its companion app uids
+ private final Map<Integer, Set<Integer>> mCompanionAppUidsMap = new ArrayMap<>();
+
final UserController mUserController;
@VisibleForTesting
public final PendingIntentController mPendingIntentController;
@@ -13707,34 +13709,10 @@
false, false, userId, "package unstartable");
break;
case Intent.ACTION_CLOSE_SYSTEM_DIALOGS:
- if (!canCloseSystemDialogs(callingPid, callingUid, callerApp)) {
- // The app can't close system dialogs, throw only if it targets S+
- if (CompatChanges.isChangeEnabled(
- ActivityManager.LOCK_DOWN_CLOSE_SYSTEM_DIALOGS, callingUid)) {
- throw new SecurityException(
- "Permission Denial: " + Intent.ACTION_CLOSE_SYSTEM_DIALOGS
- + " broadcast from " + callerPackage + " (pid="
- + callingPid + ", uid=" + callingUid + ")"
- + " requires "
- + permission.BROADCAST_CLOSE_SYSTEM_DIALOGS + ".");
- } else if (CompatChanges.isChangeEnabled(
- ActivityManager.DROP_CLOSE_SYSTEM_DIALOGS, callingUid)) {
- Slog.w(TAG, "Permission Denial: " + intent.getAction()
- + " broadcast from " + callerPackage + " (pid=" + callingPid
- + ", uid=" + callingUid + ")"
- + " requires "
- + permission.BROADCAST_CLOSE_SYSTEM_DIALOGS
- + ", dropping broadcast.");
- // Returning success seems to be the pattern here
- return ActivityManager.BROADCAST_SUCCESS;
- } else {
- Slog.w(TAG, intent.getAction()
- + " broadcast from " + callerPackage + " (pid=" + callingPid
- + ", uid=" + callingUid + ")"
- + " will require "
- + permission.BROADCAST_CLOSE_SYSTEM_DIALOGS
- + " in future builds.");
- }
+ if (!mAtmInternal.checkCanCloseSystemDialogs(callingPid, callingUid,
+ callerPackage)) {
+ // Returning success seems to be the pattern here
+ return ActivityManager.BROADCAST_SUCCESS;
}
break;
}
@@ -14029,39 +14007,6 @@
return ActivityManager.BROADCAST_SUCCESS;
}
- private boolean canCloseSystemDialogs(int pid, int uid, @Nullable ProcessRecord callerApp) {
- if (checkPermission(permission.BROADCAST_CLOSE_SYSTEM_DIALOGS, pid, uid)
- == PERMISSION_GRANTED) {
- return true;
- }
- if (callerApp == null) {
- synchronized (mPidsSelfLocked) {
- callerApp = mPidsSelfLocked.get(pid);
- }
- }
-
- if (callerApp != null) {
- // Check if the instrumentation of the process has the permission. This covers the usual
- // test started from the shell (which has the permission) case. This is needed for apps
- // targeting SDK level < S but we are also allowing for targetSdk S+ as a convenience to
- // avoid breaking a bunch of existing tests and asking them to adopt shell permissions
- // to do this.
- ActiveInstrumentation instrumentation = callerApp.getActiveInstrumentation();
- if (instrumentation != null && checkPermission(
- permission.BROADCAST_CLOSE_SYSTEM_DIALOGS, -1, instrumentation.mSourceUid)
- == PERMISSION_GRANTED) {
- return true;
- }
- // This is the notification trampoline use-case for example, where apps use Intent.ACSD
- // to close the shade prior to starting an activity.
- WindowProcessController wmApp = callerApp.getWindowProcessController();
- if (wmApp.canCloseSystemDialogsByToken()) {
- return true;
- }
- }
- return false;
- }
-
/**
* @return uid from the extra field {@link Intent#EXTRA_UID} if present, Otherwise -1
*/
@@ -16784,6 +16729,22 @@
}
@Override
+ public void setCompanionAppUids(int userId, Set<Integer> companionAppUids) {
+ synchronized (ActivityManagerService.this) {
+ mCompanionAppUidsMap.put(userId, companionAppUids);
+ }
+ }
+
+ @Override
+ public boolean isAssociatedCompanionApp(int userId, int uid) {
+ final Set<Integer> allUids = mCompanionAppUidsMap.get(userId);
+ if (allUids == null) {
+ return false;
+ }
+ return allUids.contains(uid);
+ }
+
+ @Override
public void addPendingTopUid(int uid, int pid) {
mPendingStartActivityUids.add(uid, pid);
}
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 36d4a38..9eb7c07 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -1101,15 +1101,26 @@
}
private void freezeProcess(ProcessRecord proc) {
- final int pid;
- final String name;
+ final int pid = proc.pid;
+ final String name = proc.processName;
final long unfrozenDuration;
final boolean frozen;
- synchronized (mAm) {
- pid = proc.pid;
- name = proc.processName;
+ try {
+ // pre-check for locks to avoid unnecessary freeze/unfreeze operations
+ if (Process.hasFileLocks(pid)) {
+ if (DEBUG_FREEZER) {
+ Slog.d(TAG_AM, name + " (" + pid + ") holds file locks, not freezing");
+ }
+ return;
+ }
+ } catch (IOException e) {
+ Slog.e(TAG_AM, "Not freezing. Unable to check file locks for " + name + "(" + pid
+ + "): " + e);
+ return;
+ }
+ synchronized (mAm) {
if (proc.curAdj < ProcessList.CACHED_APP_MIN_ADJ
|| proc.shouldNotFreeze) {
if (DEBUG_FREEZER) {
@@ -1141,29 +1152,50 @@
frozen = proc.frozen;
}
- if (frozen) {
- if (DEBUG_FREEZER) {
- Slog.d(TAG_AM, "froze " + pid + " " + name);
+ if (!frozen) {
+ return;
+ }
+
+
+ if (DEBUG_FREEZER) {
+ Slog.d(TAG_AM, "froze " + pid + " " + name);
+ }
+
+ EventLog.writeEvent(EventLogTags.AM_FREEZE, pid, name);
+
+ try {
+ freezeBinder(pid, true);
+ } catch (RuntimeException e) {
+ Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name);
+ proc.kill("Unable to freeze binder interface",
+ ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
+ }
+
+ // See above for why we're not taking mPhenotypeFlagLock here
+ if (mRandom.nextFloat() < mFreezerStatsdSampleRate) {
+ FrameworkStatsLog.write(FrameworkStatsLog.APP_FREEZE_CHANGED,
+ FrameworkStatsLog.APP_FREEZE_CHANGED__ACTION__FREEZE_APP,
+ pid,
+ name,
+ unfrozenDuration);
+ }
+
+ try {
+ // post-check to prevent races
+ if (Process.hasFileLocks(pid)) {
+ if (DEBUG_FREEZER) {
+ Slog.d(TAG_AM, name + " (" + pid + ") holds file locks, reverting freeze");
+ }
+
+ synchronized (mAm) {
+ unfreezeAppLocked(proc);
+ }
}
-
- EventLog.writeEvent(EventLogTags.AM_FREEZE, pid, name);
-
- try {
- freezeBinder(pid, true);
- } catch (RuntimeException e) {
- Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name);
- proc.kill("Unable to freeze binder interface",
- ApplicationExitInfo.REASON_OTHER,
- ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
- }
-
- // See above for why we're not taking mPhenotypeFlagLock here
- if (mRandom.nextFloat() < mFreezerStatsdSampleRate) {
- FrameworkStatsLog.write(FrameworkStatsLog.APP_FREEZE_CHANGED,
- FrameworkStatsLog.APP_FREEZE_CHANGED__ACTION__FREEZE_APP,
- pid,
- name,
- unfrozenDuration);
+ } catch (IOException e) {
+ Slog.e(TAG_AM, "Unable to check file locks for " + name + "(" + pid + "): " + e);
+ synchronized (mAm) {
+ unfreezeAppLocked(proc);
}
}
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 53d75d1..6f6cad0 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -711,7 +711,7 @@
mAppDataIsolationEnabled =
SystemProperties.getBoolean(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, true);
mVoldAppDataIsolationEnabled = SystemProperties.getBoolean(
- ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, true);
+ ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, false);
mAppDataIsolationWhitelistedApps = new ArrayList<>(
SystemConfig.getInstance().getAppDataIsolationWhitelistedApps());
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index e90423c..181a5cb 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1431,7 +1431,9 @@
void setActiveInstrumentation(ActiveInstrumentation instr) {
mInstr = instr;
boolean isInstrumenting = instr != null;
- mWindowProcessController.setInstrumenting(isInstrumenting,
+ mWindowProcessController.setInstrumenting(
+ isInstrumenting,
+ isInstrumenting ? instr.mSourceUid : -1,
isInstrumenting && instr.mHasBackgroundActivityStartsPermission);
}
@@ -2069,6 +2071,13 @@
}
if (!mAllowStartFgs) {
+ if (mService.mInternal != null) {
+ mAllowStartFgs = mService.mInternal.isAssociatedCompanionApp(
+ UserHandle.getUserId(info.uid), info.uid);
+ }
+ }
+
+ if (!mAllowStartFgs) {
// uid is on DeviceIdleController's user/system allowlist
// or AMS's FgsStartTempAllowList.
mAllowStartFgs = mService.isWhitelistedForFgsStartLocked(info.uid);
diff --git a/services/core/java/com/android/server/apphibernation/OWNERS b/services/core/java/com/android/server/apphibernation/OWNERS
index 4804fa3..c2e27e0 100644
--- a/services/core/java/com/android/server/apphibernation/OWNERS
+++ b/services/core/java/com/android/server/apphibernation/OWNERS
@@ -1,3 +1 @@
-# TODO: Include /core/java/android/apphibernation/OWNERS. See b/177005153
-kevhan@google.com
-rajekumar@google.com
+include /core/java/android/apphibernation/OWNERS
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index cf8bfbc..b15a886 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -38,6 +38,7 @@
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceReceiver;
+import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.PromptInfo;
import android.hardware.biometrics.SensorPropertiesInternal;
@@ -296,6 +297,19 @@
}
@Override
+ public void invalidateAuthenticatorIds(int userId, int fromSensorId,
+ IInvalidationCallback callback) throws RemoteException {
+ checkInternalPermission();
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mBiometricService.invalidateAuthenticatorIds(userId, fromSensorId, callback);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public long[] getAuthenticatorIds() throws RemoteException {
// In this method, we're not checking whether the caller is permitted to use face
// API because current authenticator ID is leaked (in a more contrived way) via Android
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index a81abcd..fd5ada0 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -42,6 +42,7 @@
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.IBiometricSysuiReceiver;
+import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.PromptInfo;
import android.hardware.biometrics.SensorPropertiesInternal;
@@ -60,6 +61,7 @@
import android.provider.Settings;
import android.security.KeyStore;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.Pair;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -78,6 +80,7 @@
import java.util.List;
import java.util.Map;
import java.util.Random;
+import java.util.Set;
/**
* System service that arbitrates the modality for BiometricPrompt to use.
@@ -240,6 +243,72 @@
}
};
+ /**
+ * Tracks authenticatorId invalidation. For more details, see
+ * {@link com.android.server.biometrics.sensors.InvalidationRequesterClient}.
+ */
+ @VisibleForTesting
+ static class InvalidationTracker {
+ @NonNull private final IInvalidationCallback mClientCallback;
+ @NonNull private final Set<Integer> mSensorsPendingInvalidation;
+
+ public static InvalidationTracker start(@NonNull ArrayList<BiometricSensor> sensors,
+ int userId, int fromSensorId, @NonNull IInvalidationCallback clientCallback) {
+ return new InvalidationTracker(sensors, userId, fromSensorId, clientCallback);
+ }
+
+ private InvalidationTracker(@NonNull ArrayList<BiometricSensor> sensors, int userId,
+ int fromSensorId, @NonNull IInvalidationCallback clientCallback) {
+ mClientCallback = clientCallback;
+ mSensorsPendingInvalidation = new ArraySet<>();
+
+ for (BiometricSensor sensor : sensors) {
+ if (sensor.id == fromSensorId) {
+ continue;
+ }
+
+ if (!Utils.isAtLeastStrength(sensor.oemStrength, Authenticators.BIOMETRIC_STRONG)) {
+ continue;
+ }
+
+ Slog.d(TAG, "Requesting authenticatorId invalidation for sensor: " + sensor.id);
+
+ synchronized (this) {
+ mSensorsPendingInvalidation.add(sensor.id);
+ }
+
+ try {
+ sensor.impl.invalidateAuthenticatorId(userId, new IInvalidationCallback.Stub() {
+ @Override
+ public void onCompleted() {
+ onInvalidated(sensor.id);
+ }
+ });
+ } catch (RemoteException e) {
+ Slog.d(TAG, "RemoteException", e);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void onInvalidated(int sensorId) {
+ synchronized (this) {
+ mSensorsPendingInvalidation.remove(sensorId);
+
+ Slog.d(TAG, "Sensor " + sensorId + " invalidated, remaining size: "
+ + mSensorsPendingInvalidation.size());
+
+ if (mSensorsPendingInvalidation.isEmpty()) {
+ try {
+ mClientCallback.onCompleted();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote Exception", e);
+ }
+ }
+ }
+ }
+ }
+
@VisibleForTesting
public static class SettingObserver extends ContentObserver {
@@ -668,6 +737,14 @@
}
}
+ @Override
+ public void invalidateAuthenticatorIds(int userId, int fromSensorId,
+ IInvalidationCallback callback) {
+ checkInternalPermission();
+
+ InvalidationTracker.start(mSensors, userId, fromSensorId, callback);
+ }
+
@Override // Binder call
public long[] getAuthenticatorIds(int callingUserId) {
checkInternalPermission();
diff --git a/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java
index b8084d5..fe946cb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java
@@ -21,6 +21,8 @@
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricsProtoEnums;
+import java.util.Map;
+
/**
* ClientMonitor subclass for requesting authenticatorId invalidation. See
* {@link InvalidationRequesterClient} for more info.
@@ -29,18 +31,21 @@
extends ClientMonitor<T> {
private final BiometricUtils<S> mUtils;
+ private final Map<Integer, Long> mAuthenticatorIds;
public InvalidationClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
- int userId, int sensorId, @NonNull BiometricUtils<S> utils) {
+ int userId, int sensorId, @NonNull BiometricUtils<S> utils,
+ @NonNull Map<Integer, Long> authenticatorIds) {
super(context, lazyDaemon, null /* token */, null /* listener */, userId,
context.getOpPackageName(), 0 /* cookie */, sensorId,
BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
BiometricsProtoEnums.CLIENT_UNKNOWN);
mUtils = utils;
+ mAuthenticatorIds = authenticatorIds;
}
public void onAuthenticatorIdInvalidated(long newAuthenticatorId) {
- // TODO: Update framework w/ newAuthenticatorId
+ mAuthenticatorIds.put(getTargetUserId(), newAuthenticatorId);
mCallback.onClientFinished(this, true /* success */);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java b/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java
index ca34eee..d95fa23 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java
@@ -18,8 +18,10 @@
import android.annotation.NonNull;
import android.content.Context;
+import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.IInvalidationCallback;
/**
* ClientMonitor subclass responsible for coordination of authenticatorId invalidation of other
@@ -53,25 +55,39 @@
* switches, the framework can check if any sensor has the "invalidationInProgress" flag set. If so,
* the framework should re-start the invalidation process described above.
*/
-public abstract class InvalidationRequesterClient<T> extends ClientMonitor<T> {
+public abstract class InvalidationRequesterClient<S extends BiometricAuthenticator.Identifier, T>
+ extends ClientMonitor<T> {
private final BiometricManager mBiometricManager;
+ @NonNull private final BiometricUtils<S> mUtils;
+
+ @NonNull private final IInvalidationCallback mInvalidationCallback =
+ new IInvalidationCallback.Stub() {
+ @Override
+ public void onCompleted() {
+ mUtils.setInvalidationInProgress(getContext(), getTargetUserId(),
+ false /* inProgress */);
+ mCallback.onClientFinished(InvalidationRequesterClient.this, true /* success */);
+ }
+ };
public InvalidationRequesterClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
- int userId, int sensorId) {
+ int userId, int sensorId, @NonNull BiometricUtils<S> utils) {
super(context, lazyDaemon, null /* token */, null /* listener */, userId,
context.getOpPackageName(), 0 /* cookie */, sensorId,
BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
BiometricsProtoEnums.CLIENT_UNKNOWN);
mBiometricManager = context.getSystemService(BiometricManager.class);
+ mUtils = utils;
}
@Override
public void start(@NonNull Callback callback) {
super.start(callback);
- // TODO(b/159667191): Request BiometricManager/BiometricService to invalidate
- // authenticatorIds. Be sure to invoke BiometricUtils#setInvalidationInProgress(true)
+ mUtils.setInvalidationInProgress(getContext(), getTargetUserId(), true /* inProgress */);
+ mBiometricManager.invalidateAuthenticatorIds(getTargetUserId(), getSensorId(),
+ mInvalidationCallback);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
index f07bf1e..54ab2e5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
@@ -19,13 +19,13 @@
import android.annotation.NonNull;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IBiometricSensorReceiver;
+import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.SensorPropertiesInternal;
import android.hardware.face.IFaceService;
import android.os.IBinder;
import android.os.RemoteException;
-import com.android.server.biometrics.SensorConfig;
import com.android.server.biometrics.sensors.LockoutTracker;
/**
@@ -87,6 +87,12 @@
}
@Override
+ public void invalidateAuthenticatorId(int userId, IInvalidationCallback callback)
+ throws RemoteException {
+ mFaceService.invalidateAuthenticatorId(mSensorId, userId, callback);
+ }
+
+ @Override
public @LockoutTracker.LockoutMode int getLockoutModeForUser(int userId)
throws RemoteException {
return mFaceService.getLockoutModeForUser(mSensorId, userId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index cb56e8c..f055d55 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -29,6 +29,7 @@
import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
+import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.SensorProps;
@@ -54,10 +55,10 @@
import com.android.server.ServiceThread;
import com.android.server.SystemService;
import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.BiometricServiceCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
-import com.android.server.biometrics.sensors.BiometricServiceCallback;
import com.android.server.biometrics.sensors.face.aidl.FaceProvider;
import com.android.server.biometrics.sensors.face.hidl.Face10;
@@ -503,6 +504,19 @@
return provider.getLockoutModeForUser(sensorId, userId);
}
+ @Override
+ public void invalidateAuthenticatorId(int sensorId, int userId,
+ IInvalidationCallback callback) {
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+
+ final ServiceProvider provider = getProviderForSensor(sensorId);
+ if (provider == null) {
+ Slog.w(TAG, "Null provider for invalidateAuthenticatorId");
+ return;
+ }
+ provider.scheduleInvalidateAuthenticatorId(sensorId, userId, callback);
+ }
+
@Override // Binder call
public long getAuthenticatorId(int sensorId, int userId) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
index be4e248..51b427d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.face.Face;
import android.hardware.face.FaceManager;
@@ -71,6 +72,16 @@
@LockoutTracker.LockoutMode
int getLockoutModeForUser(int sensorId, int userId);
+ /**
+ * Requests for the authenticatorId (whose source of truth is in the TEE or equivalent) to
+ * be invalidated. See {@link com.android.server.biometrics.sensors.InvalidationRequesterClient}
+ */
+ default void scheduleInvalidateAuthenticatorId(int sensorId, int userId,
+ @NonNull IInvalidationCallback callback) {
+ throw new IllegalStateException("Providers that support invalidation must override"
+ + " this method");
+ }
+
long getAuthenticatorId(int sensorId, int userId);
boolean isHardwareDetected(int sensorId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java
index f512cef..9c6438e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java
@@ -16,10 +16,9 @@
package com.android.server.biometrics.sensors.face.aidl;
+import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.face.ISession;
-
-import android.annotation.NonNull;
import android.hardware.face.Face;
import android.os.RemoteException;
import android.util.Slog;
@@ -27,13 +26,15 @@
import com.android.server.biometrics.sensors.InvalidationClient;
import com.android.server.biometrics.sensors.face.FaceUtils;
+import java.util.Map;
+
public class FaceInvalidationClient extends InvalidationClient<Face, ISession> {
private static final String TAG = "FaceInvalidationClient";
public FaceInvalidationClient(@NonNull Context context,
@NonNull LazyDaemon<ISession> lazyDaemon, int userId, int sensorId,
- @NonNull FaceUtils utils) {
- super(context, lazyDaemon, userId, sensorId, utils);
+ @NonNull FaceUtils utils, @NonNull Map<Integer, Long> authenticatorIds) {
+ super(context, lazyDaemon, userId, sensorId, utils, authenticatorIds);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
index d4cdc8b..312a3ba 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
@@ -19,13 +19,13 @@
import android.annotation.NonNull;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IBiometricSensorReceiver;
+import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.SensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintService;
import android.os.IBinder;
import android.os.RemoteException;
-import com.android.server.biometrics.SensorConfig;
import com.android.server.biometrics.sensors.LockoutTracker;
/**
@@ -94,6 +94,12 @@
}
@Override
+ public void invalidateAuthenticatorId(int userId, IInvalidationCallback callback)
+ throws RemoteException {
+ mFingerprintService.invalidateAuthenticatorId(mSensorId, userId, callback);
+ }
+
+ @Override
public long getAuthenticatorId(int callingUserId) throws RemoteException {
return mFingerprintService.getAuthenticatorId(mSensorId, callingUserId);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index e6cdbd2..d541eb3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -36,6 +36,7 @@
import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
+import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.fingerprint.IFingerprint;
import android.hardware.biometrics.fingerprint.SensorProps;
@@ -571,6 +572,19 @@
return provider.getLockoutModeForUser(sensorId, userId);
}
+ @Override
+ public void invalidateAuthenticatorId(int sensorId, int userId,
+ IInvalidationCallback callback) {
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+
+ final ServiceProvider provider = getProviderForSensor(sensorId);
+ if (provider == null) {
+ Slog.w(TAG, "Null provider for invalidateAuthenticatorId");
+ return;
+ }
+ provider.scheduleInvalidateAuthenticatorId(sensorId, userId, callback);
+ }
+
@Override // Binder call
public long getAuthenticatorId(int sensorId, int userId) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index d94c984..272e2b2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
@@ -109,6 +110,16 @@
@LockoutTracker.LockoutMode
int getLockoutModeForUser(int sensorId, int userId);
+ /**
+ * Requests for the authenticatorId (whose source of truth is in the TEE or equivalent) to
+ * be invalidated. See {@link com.android.server.biometrics.sensors.InvalidationRequesterClient}
+ */
+ default void scheduleInvalidateAuthenticatorId(int sensorId, int userId,
+ @NonNull IInvalidationCallback callback) {
+ throw new IllegalStateException("Providers that support invalidation must override"
+ + " this method");
+ }
+
long getAuthenticatorId(int sensorId, int userId);
void onPointerDown(int sensorId, int x, int y, float minor, float major);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java
index b6d8892..3d07334 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java
@@ -26,13 +26,15 @@
import com.android.server.biometrics.sensors.InvalidationClient;
import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
+import java.util.Map;
+
public class FingerprintInvalidationClient extends InvalidationClient<Fingerprint, ISession> {
private static final String TAG = "FingerprintInvalidationClient";
public FingerprintInvalidationClient(@NonNull Context context,
@NonNull LazyDaemon<ISession> lazyDaemon, int userId, int sensorId,
- @NonNull FingerprintUtils utils) {
- super(context, lazyDaemon, userId, sensorId, utils);
+ @NonNull FingerprintUtils utils, @NonNull Map<Integer, Long> authenticatorIds) {
+ super(context, lazyDaemon, userId, sensorId, utils, authenticatorIds);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
index 6f437a7..3e5b88c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
@@ -19,13 +19,13 @@
import android.annotation.NonNull;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IBiometricSensorReceiver;
+import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.SensorPropertiesInternal;
import android.hardware.iris.IIrisService;
import android.os.IBinder;
import android.os.RemoteException;
-import com.android.server.biometrics.SensorConfig;
import com.android.server.biometrics.sensors.LockoutTracker;
/**
@@ -86,6 +86,10 @@
}
@Override
+ public void invalidateAuthenticatorId(int userId, IInvalidationCallback callback) {
+ }
+
+ @Override
public long getAuthenticatorId(int callingUserId) throws RemoteException {
return 0;
}
diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java
index 18907a1..9ba957e 100644
--- a/services/core/java/com/android/server/compat/CompatChange.java
+++ b/services/core/java/com/android/server/compat/CompatChange.java
@@ -64,7 +64,7 @@
private Map<String, Boolean> mDeferredOverrides;
public CompatChange(long changeId) {
- this(changeId, null, -1, -1, false, false, null);
+ this(changeId, null, -1, -1, false, false, null, false);
}
/**
@@ -77,9 +77,10 @@
* @param disabled If {@code true}, overrides any {@code enableAfterTargetSdk} set.
*/
public CompatChange(long changeId, @Nullable String name, int enableAfterTargetSdk,
- int enableSinceTargetSdk, boolean disabled, boolean loggingOnly, String description) {
+ int enableSinceTargetSdk, boolean disabled, boolean loggingOnly, String description,
+ boolean overridable) {
super(changeId, name, enableAfterTargetSdk, enableSinceTargetSdk, disabled, loggingOnly,
- description);
+ description, overridable);
}
/**
@@ -88,7 +89,7 @@
public CompatChange(Change change) {
super(change.getId(), change.getName(), change.getEnableAfterTargetSdk(),
change.getEnableSinceTargetSdk(), change.getDisabled(), change.getLoggingOnly(),
- change.getDescription());
+ change.getDescription(), change.getOverridable());
}
void registerListener(ChangeListener listener) {
@@ -274,6 +275,9 @@
if (mDeferredOverrides != null && mDeferredOverrides.size() > 0) {
sb.append("; deferredOverrides=").append(mDeferredOverrides);
}
+ if (getOverridable()) {
+ sb.append("; overridable");
+ }
return sb.append(")").toString();
}
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
index 2c06d82..b5d875d 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
@@ -122,6 +122,8 @@
public IpConnectivityMetrics(Context ctx, ToIntFunction<Context> capacityGetter) {
super(ctx);
+ // Load JNI libraries used by the IpConnectivityMetrics service and its dependencies
+ System.loadLibrary("service-connectivity");
mCapacityGetter = capacityGetter;
initBuffer();
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 7bde4d5..55d8279 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -202,28 +202,28 @@
// either the linger timeout expiring and the network being taken down, or the network
// satisfying a request again.
public static class LingerTimer implements Comparable<LingerTimer> {
- public final NetworkRequest request;
+ public final int requestId;
public final long expiryMs;
- public LingerTimer(NetworkRequest request, long expiryMs) {
- this.request = request;
+ public LingerTimer(int requestId, long expiryMs) {
+ this.requestId = requestId;
this.expiryMs = expiryMs;
}
public boolean equals(Object o) {
if (!(o instanceof LingerTimer)) return false;
LingerTimer other = (LingerTimer) o;
- return (request.requestId == other.request.requestId) && (expiryMs == other.expiryMs);
+ return (requestId == other.requestId) && (expiryMs == other.expiryMs);
}
public int hashCode() {
- return Objects.hash(request.requestId, expiryMs);
+ return Objects.hash(requestId, expiryMs);
}
public int compareTo(LingerTimer other) {
return (expiryMs != other.expiryMs) ?
Long.compare(expiryMs, other.expiryMs) :
- Integer.compare(request.requestId, other.request.requestId);
+ Integer.compare(requestId, other.requestId);
}
public String toString() {
- return String.format("%s, expires %dms", request.toString(),
+ return String.format("%s, expires %dms", requestId,
expiryMs - SystemClock.elapsedRealtime());
}
}
@@ -693,7 +693,7 @@
updateRequestCounts(REMOVE, existing);
mNetworkRequests.remove(requestId);
if (existing.isRequest()) {
- unlingerRequest(existing);
+ unlingerRequest(existing.requestId);
}
}
@@ -839,33 +839,33 @@
}
/**
- * Sets the specified request to linger on this network for the specified time. Called by
+ * Sets the specified requestId to linger on this network for the specified time. Called by
* ConnectivityService when the request is moved to another network with a higher score.
*/
- public void lingerRequest(NetworkRequest request, long now, long duration) {
- if (mLingerTimerForRequest.get(request.requestId) != null) {
+ public void lingerRequest(int requestId, long now, long duration) {
+ if (mLingerTimerForRequest.get(requestId) != null) {
// Cannot happen. Once a request is lingering on a particular network, we cannot
// re-linger it unless that network becomes the best for that request again, in which
// case we should have unlingered it.
- Log.wtf(TAG, toShortString() + ": request " + request.requestId + " already lingered");
+ Log.wtf(TAG, toShortString() + ": request " + requestId + " already lingered");
}
final long expiryMs = now + duration;
- LingerTimer timer = new LingerTimer(request, expiryMs);
+ LingerTimer timer = new LingerTimer(requestId, expiryMs);
if (VDBG) Log.d(TAG, "Adding LingerTimer " + timer + " to " + toShortString());
mLingerTimers.add(timer);
- mLingerTimerForRequest.put(request.requestId, timer);
+ mLingerTimerForRequest.put(requestId, timer);
}
/**
* Cancel lingering. Called by ConnectivityService when a request is added to this network.
- * Returns true if the given request was lingering on this network, false otherwise.
+ * Returns true if the given requestId was lingering on this network, false otherwise.
*/
- public boolean unlingerRequest(NetworkRequest request) {
- LingerTimer timer = mLingerTimerForRequest.get(request.requestId);
+ public boolean unlingerRequest(int requestId) {
+ LingerTimer timer = mLingerTimerForRequest.get(requestId);
if (timer != null) {
if (VDBG) Log.d(TAG, "Removing LingerTimer " + timer + " from " + toShortString());
mLingerTimers.remove(timer);
- mLingerTimerForRequest.remove(request.requestId);
+ mLingerTimerForRequest.remove(requestId);
return true;
}
return false;
diff --git a/services/core/java/com/android/server/connectivity/PacManager.java b/services/core/java/com/android/server/connectivity/PacManager.java
index 06721ae..93930ae 100644
--- a/services/core/java/com/android/server/connectivity/PacManager.java
+++ b/services/core/java/com/android/server/connectivity/PacManager.java
@@ -177,7 +177,7 @@
* @param proxy Proxy information that is about to be broadcast.
* @return Returns whether the broadcast should be sent : either DO_ or DONT_SEND_BROADCAST
*/
- synchronized boolean setCurrentProxyScriptUrl(ProxyInfo proxy) {
+ public synchronized boolean setCurrentProxyScriptUrl(ProxyInfo proxy) {
if (!Uri.EMPTY.equals(proxy.getPacFileUrl())) {
if (proxy.getPacFileUrl().equals(mPacUrl) && (proxy.getPort() > 0)) {
// Allow to send broadcast, nothing to do.
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 07a4b89..b250f16 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -1850,34 +1850,6 @@
}
}
- /**
- * @param uid The target uid.
- *
- * @return {@code true} if {@code uid} is included in one of the mBlockedUidsAsToldToNetd
- * ranges and the VPN is not connected, or if the VPN is connected but does not apply to
- * the {@code uid}.
- *
- * @apiNote This method don't check VPN lockdown status.
- * @see #mBlockedUidsAsToldToConnectivity
- */
- public synchronized boolean isBlockingUid(int uid) {
- if (mNetworkInfo.isConnected()) {
- return !appliesToUid(uid);
- } else {
- return containsUid(mBlockedUidsAsToldToConnectivity, uid);
- }
- }
-
- private boolean containsUid(Collection<UidRangeParcel> ranges, int uid) {
- if (ranges == null) return false;
- for (UidRangeParcel range : ranges) {
- if (range.start <= uid && uid <= range.stop) {
- return true;
- }
- }
- return false;
- }
-
private void updateAlwaysOnNotification(DetailedState networkState) {
final boolean visible = (mAlwaysOn && networkState != DetailedState.CONNECTED);
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 9591894..006f875 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -52,9 +52,12 @@
import com.android.server.utils.DeviceConfigInterface;
import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Date;
import java.util.List;
+import java.util.Locale;
import java.util.Objects;
@@ -1299,7 +1302,8 @@
// mShouldObserveAmbientHighChange is true, screen is on, peak refresh rate
// changeable and low power mode off. After initialization, these states will
// be updated from the same handler thread.
- private boolean mDefaultDisplayOn = false;
+ private int mDefaultDisplayState = Display.STATE_UNKNOWN;
+ private boolean mIsDeviceActive = false;
private boolean mRefreshRateChangeable = false;
private boolean mLowPowerModeEnabled = false;
@@ -1480,7 +1484,8 @@
pw.println(" BrightnessObserver");
pw.println(" mAmbientLux: " + mAmbientLux);
pw.println(" mBrightness: " + mBrightness);
- pw.println(" mDefaultDisplayOn: " + mDefaultDisplayOn);
+ pw.println(" mDefaultDisplayState: " + mDefaultDisplayState);
+ pw.println(" mIsDeviceActive: " + mIsDeviceActive);
pw.println(" mLowPowerModeEnabled: " + mLowPowerModeEnabled);
pw.println(" mRefreshRateChangeable: " + mRefreshRateChangeable);
pw.println(" mShouldObserveDisplayLowChange: " + mShouldObserveDisplayLowChange);
@@ -1706,14 +1711,17 @@
private void updateDefaultDisplayState() {
Display display = mContext.getSystemService(DisplayManager.class)
.getDisplay(Display.DEFAULT_DISPLAY);
- boolean defaultDisplayOn = display != null && display.getState() != Display.STATE_OFF;
- setDefaultDisplayState(defaultDisplayOn);
+ if (display == null) {
+ return;
+ }
+
+ setDefaultDisplayState(display.getState());
}
@VisibleForTesting
- public void setDefaultDisplayState(boolean on) {
- if (mDefaultDisplayOn != on) {
- mDefaultDisplayOn = on;
+ public void setDefaultDisplayState(int state) {
+ if (mDefaultDisplayState != state) {
+ mDefaultDisplayState = state;
updateSensorStatus();
}
}
@@ -1734,15 +1742,19 @@
}
private boolean isDeviceActive() {
- return mDefaultDisplayOn && mInjector.isDeviceInteractive(mContext);
+ mIsDeviceActive = mInjector.isDeviceInteractive(mContext);
+ return (mDefaultDisplayState == Display.STATE_ON)
+ && mIsDeviceActive;
}
private final class LightSensorEventListener implements SensorEventListener {
final private static int INJECT_EVENTS_INTERVAL_MS = LIGHT_SENSOR_RATE_MS;
private float mLastSensorData;
+ private long mTimestamp;
public void dumpLocked(PrintWriter pw) {
pw.println(" mLastSensorData: " + mLastSensorData);
+ pw.println(" mTimestamp: " + formatTimestamp(mTimestamp));
}
@Override
@@ -1766,6 +1778,7 @@
}
long now = SystemClock.uptimeMillis();
+ mTimestamp = System.currentTimeMillis();
if (mAmbientFilter != null) {
mAmbientFilter.addValue(now, mLastSensorData);
}
@@ -1792,6 +1805,12 @@
mHandler.removeCallbacks(mInjectSensorEventRunnable);
}
+ private String formatTimestamp(long time) {
+ SimpleDateFormat dateFormat =
+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US);
+ return dateFormat.format(new Date(time));
+ }
+
private void processSensorData(long now) {
if (mAmbientFilter != null) {
mAmbientLux = mAmbientFilter.getEstimate(now);
diff --git a/services/core/java/com/android/server/graphics/fonts/OWNERS b/services/core/java/com/android/server/graphics/fonts/OWNERS
new file mode 100644
index 0000000..34ac813
--- /dev/null
+++ b/services/core/java/com/android/server/graphics/fonts/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 24939
+
+include /graphics/java/android/graphics/fonts/OWNERS
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
index 407cedf..141fa6a 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
@@ -44,12 +44,6 @@
public abstract boolean isUidRestrictedOnMeteredNetworks(int uid);
/**
- * @return true if networking is blocked on the given interface for the given uid according
- * to current networking policies.
- */
- public abstract boolean isUidNetworkingBlocked(int uid, String ifname);
-
- /**
* Figure out if networking is blocked for a given set of conditions.
*
* This is used by ConnectivityService via passing stale copies of conditions, so it must not
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 0e7b4b8..5ed7a96 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -3862,6 +3862,13 @@
}
}
+ @VisibleForTesting
+ boolean isRestrictedModeEnabled() {
+ synchronized (mUidRulesFirstLock) {
+ return mRestrictedNetworkingMode;
+ }
+ }
+
/**
* updates restricted mode state / access for all apps
* Called on initialization and when restricted mode is enabled / disabled.
@@ -4489,26 +4496,26 @@
final boolean isDenied = (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0;
final boolean isAllowed = (uidPolicy & POLICY_ALLOW_METERED_BACKGROUND) != 0;
- final int oldRule = oldUidRules & MASK_METERED_NETWORKS;
- int newRule = RULE_NONE;
+
+ // copy oldUidRules and clear out METERED_NETWORKS rules.
+ int newUidRules = oldUidRules & (~MASK_METERED_NETWORKS);
// First step: define the new rule based on user restrictions and foreground state.
if (isRestrictedByAdmin) {
- newRule = RULE_REJECT_METERED;
+ newUidRules |= RULE_REJECT_METERED;
} else if (isForeground) {
if (isDenied || (mRestrictBackground && !isAllowed)) {
- newRule = RULE_TEMPORARY_ALLOW_METERED;
+ newUidRules |= RULE_TEMPORARY_ALLOW_METERED;
} else if (isAllowed) {
- newRule = RULE_ALLOW_METERED;
+ newUidRules |= RULE_ALLOW_METERED;
}
} else {
if (isDenied) {
- newRule = RULE_REJECT_METERED;
+ newUidRules |= RULE_REJECT_METERED;
} else if (mRestrictBackground && isAllowed) {
- newRule = RULE_ALLOW_METERED;
+ newUidRules |= RULE_ALLOW_METERED;
}
}
- final int newUidRules = newRule | (oldUidRules & MASK_ALL_NETWORKS);
if (LOGV) {
Log.v(TAG, "updateRuleForRestrictBackgroundUL(" + uid + ")"
@@ -4516,8 +4523,8 @@
+ ", isDenied=" + isDenied
+ ", isAllowed=" + isAllowed
+ ", isRestrictedByAdmin=" + isRestrictedByAdmin
- + ", oldRule=" + uidRulesToString(oldRule)
- + ", newRule=" + uidRulesToString(newRule)
+ + ", oldRule=" + uidRulesToString(oldUidRules & MASK_METERED_NETWORKS)
+ + ", newRule=" + uidRulesToString(newUidRules & MASK_METERED_NETWORKS)
+ ", newUidRules=" + uidRulesToString(newUidRules)
+ ", oldUidRules=" + uidRulesToString(oldUidRules));
}
@@ -4529,8 +4536,8 @@
}
// Second step: apply bw changes based on change of state.
- if (newRule != oldRule) {
- if (hasRule(newRule, RULE_TEMPORARY_ALLOW_METERED)) {
+ if (newUidRules != oldUidRules) {
+ if (hasRule(newUidRules, RULE_TEMPORARY_ALLOW_METERED)) {
// Temporarily allow foreground app, removing from denylist if necessary
// (since bw_penalty_box prevails over bw_happy_box).
@@ -4541,7 +4548,7 @@
if (isDenied) {
setMeteredNetworkDenylist(uid, false);
}
- } else if (hasRule(oldRule, RULE_TEMPORARY_ALLOW_METERED)) {
+ } else if (hasRule(oldUidRules, RULE_TEMPORARY_ALLOW_METERED)) {
// Remove temporary exemption from app that is not on foreground anymore.
// TODO: if statements below are used to avoid unnecessary calls to netd / iptables,
@@ -4554,18 +4561,18 @@
if (isDenied || isRestrictedByAdmin) {
setMeteredNetworkDenylist(uid, true);
}
- } else if (hasRule(newRule, RULE_REJECT_METERED)
- || hasRule(oldRule, RULE_REJECT_METERED)) {
+ } else if (hasRule(newUidRules, RULE_REJECT_METERED)
+ || hasRule(oldUidRules, RULE_REJECT_METERED)) {
// Flip state because app was explicitly added or removed to denylist.
setMeteredNetworkDenylist(uid, (isDenied || isRestrictedByAdmin));
- if (hasRule(oldRule, RULE_REJECT_METERED) && isAllowed) {
+ if (hasRule(oldUidRules, RULE_REJECT_METERED) && isAllowed) {
// Since denial prevails over allowance, we need to handle the special case
// where app is allowed and denied at the same time (although such
// scenario should be blocked by the UI), then it is removed from the denylist.
setMeteredNetworkAllowlist(uid, isAllowed);
}
- } else if (hasRule(newRule, RULE_ALLOW_METERED)
- || hasRule(oldRule, RULE_ALLOW_METERED)) {
+ } else if (hasRule(newUidRules, RULE_ALLOW_METERED)
+ || hasRule(oldUidRules, RULE_ALLOW_METERED)) {
// Flip state because app was explicitly added or removed to allowlist.
setMeteredNetworkAllowlist(uid, isAllowed);
} else {
@@ -4651,8 +4658,9 @@
final boolean isForeground = isUidForegroundOnRestrictPowerUL(uid);
final boolean isWhitelisted = isWhitelistedFromPowerSaveUL(uid, mDeviceIdleMode);
- final int oldRule = oldUidRules & MASK_ALL_NETWORKS;
- int newRule = RULE_NONE;
+
+ // Copy existing uid rules and clear ALL_NETWORK rules.
+ int newUidRules = oldUidRules & (~MASK_ALL_NETWORKS);
// First step: define the new rule based on user restrictions and foreground state.
@@ -4660,14 +4668,12 @@
// by considering the foreground and non-foreground states.
if (isForeground) {
if (restrictMode) {
- newRule = RULE_ALLOW_ALL;
+ newUidRules |= RULE_ALLOW_ALL;
}
} else if (restrictMode) {
- newRule = isWhitelisted ? RULE_ALLOW_ALL : RULE_REJECT_ALL;
+ newUidRules |= isWhitelisted ? RULE_ALLOW_ALL : RULE_REJECT_ALL;
}
- final int newUidRules = (oldUidRules & MASK_METERED_NETWORKS) | newRule;
-
if (LOGV) {
Log.v(TAG, "updateRulesForPowerRestrictionsUL(" + uid + ")"
+ ", isIdle: " + isUidIdle
@@ -4675,17 +4681,18 @@
+ ", mDeviceIdleMode: " + mDeviceIdleMode
+ ", isForeground=" + isForeground
+ ", isWhitelisted=" + isWhitelisted
- + ", oldRule=" + uidRulesToString(oldRule)
- + ", newRule=" + uidRulesToString(newRule)
+ + ", oldRule=" + uidRulesToString(oldUidRules & MASK_ALL_NETWORKS)
+ + ", newRule=" + uidRulesToString(newUidRules & MASK_ALL_NETWORKS)
+ ", newUidRules=" + uidRulesToString(newUidRules)
+ ", oldUidRules=" + uidRulesToString(oldUidRules));
}
// Second step: notify listeners if state changed.
- if (newRule != oldRule) {
- if (newRule == RULE_NONE || hasRule(newRule, RULE_ALLOW_ALL)) {
+ if (newUidRules != oldUidRules) {
+ if ((newUidRules & MASK_ALL_NETWORKS) == RULE_NONE || hasRule(newUidRules,
+ RULE_ALLOW_ALL)) {
if (LOGV) Log.v(TAG, "Allowing non-metered access for UID " + uid);
- } else if (hasRule(newRule, RULE_REJECT_ALL)) {
+ } else if (hasRule(newUidRules, RULE_REJECT_ALL)) {
if (LOGV) Log.v(TAG, "Rejecting non-metered access for UID " + uid);
} else {
// All scenarios should have been covered above
@@ -5352,7 +5359,7 @@
public boolean isUidNetworkingBlocked(int uid, boolean isNetworkMetered) {
final long startTime = mStatLogger.getTime();
- mContext.enforceCallingOrSelfPermission(OBSERVE_NETWORK_POLICY, TAG);
+ enforceAnyPermissionOf(OBSERVE_NETWORK_POLICY, PERMISSION_MAINLINE_NETWORK_STACK);
final int uidRules;
final boolean isBackgroundRestricted;
synchronized (mUidRulesFirstLock) {
@@ -5451,32 +5458,6 @@
&& !hasRule(uidRules, RULE_TEMPORARY_ALLOW_METERED);
}
- /**
- * @return true if networking is blocked on the given interface for the given uid according
- * to current networking policies.
- */
- @Override
- public boolean isUidNetworkingBlocked(int uid, String ifname) {
- final long startTime = mStatLogger.getTime();
-
- final int uidRules;
- final boolean isBackgroundRestricted;
- synchronized (mUidRulesFirstLock) {
- uidRules = mUidRules.get(uid, RULE_NONE);
- isBackgroundRestricted = mRestrictBackground;
- }
- final boolean isNetworkMetered;
- synchronized (mMeteredIfacesLock) {
- isNetworkMetered = mMeteredIfaces.contains(ifname);
- }
- final boolean ret = isUidNetworkingBlockedInternal(uid, uidRules, isNetworkMetered,
- isBackgroundRestricted, mLogger);
-
- mStatLogger.logDurationStat(Stats.IS_UID_NETWORKING_BLOCKED, startTime);
-
- return ret;
- }
-
@Override
public void onTempPowerSaveWhitelistChange(int appId, boolean added) {
synchronized (mUidRulesFirstLock) {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java b/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java
index 7bcf318..47bb8f0 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java
@@ -119,6 +119,8 @@
switch(type) {
case "restrict-background":
return getRestrictBackground();
+ case "restricted-mode":
+ return getRestrictedModeState();
}
pw.println("Error: unknown get type '" + type + "'");
return -1;
@@ -255,6 +257,13 @@
return listUidList("App Idle whitelisted UIDs", uids);
}
+ private int getRestrictedModeState() {
+ final PrintWriter pw = getOutPrintWriter();
+ pw.print("Restricted mode status: ");
+ pw.println(mInterface.isRestrictedModeEnabled() ? "enabled" : "disabled");
+ return 0;
+ }
+
private int getRestrictBackground() throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
pw.print("Restrict background status: ");
diff --git a/services/core/java/com/android/server/pm/DumpState.java b/services/core/java/com/android/server/pm/DumpState.java
index 520871f..4f986bd 100644
--- a/services/core/java/com/android/server/pm/DumpState.java
+++ b/services/core/java/com/android/server/pm/DumpState.java
@@ -44,6 +44,7 @@
public static final int DUMP_APEX = 1 << 25;
public static final int DUMP_QUERIES = 1 << 26;
public static final int DUMP_KNOWN_PACKAGES = 1 << 27;
+ public static final int DUMP_PER_UID_READ_TIMEOUTS = 1 << 28;
public static final int OPTION_SHOW_FILTERS = 1 << 0;
public static final int OPTION_DUMP_ALL_COMPONENTS = 1 << 1;
diff --git a/services/core/java/com/android/server/pm/MULTIUSER_AND_ENTERPRISE_OWNERS b/services/core/java/com/android/server/pm/MULTIUSER_AND_ENTERPRISE_OWNERS
new file mode 100644
index 0000000..a52e9cf
--- /dev/null
+++ b/services/core/java/com/android/server/pm/MULTIUSER_AND_ENTERPRISE_OWNERS
@@ -0,0 +1,7 @@
+# OWNERS of Multiuser related files related to Enterprise
+
+include /MULTIUSER_OWNERS
+
+# Enterprise owners
+rubinxu@google.com
+sandness@google.com
diff --git a/services/core/java/com/android/server/pm/OWNERS b/services/core/java/com/android/server/pm/OWNERS
index 004259b..43c5d5e 100644
--- a/services/core/java/com/android/server/pm/OWNERS
+++ b/services/core/java/com/android/server/pm/OWNERS
@@ -30,13 +30,12 @@
per-file CrossProfileAppsService.java = omakoto@google.com, yamasani@google.com
per-file CrossProfileIntentFilter.java = omakoto@google.com, yamasani@google.com
per-file CrossProfileIntentResolver.java = omakoto@google.com, yamasani@google.com
-per-file RestrictionsSet.java = bookatz@google.com, omakoto@google.com, yamasani@google.com, rubinxu@google.com, sandness@google.com
-per-file UserManagerInternal.java = bookatz@google.com, omakoto@google.com, yamasani@google.com
-per-file UserManagerService.java = bookatz@google.com, omakoto@google.com, yamasani@google.com
-per-file UserRestrictionsUtils.java = omakoto@google.com, rubinxu@google.com, sandness@google.com, yamasani@google.com
-per-file UserSystemPackageInstaller.java = bookatz@google.com, omakoto@google.com, yamasani@google.com
-per-file UserTypeDetails.java = bookatz@google.com, omakoto@google.com, yamasani@google.com
-per-file UserTypeFactory.java = bookatz@google.com, omakoto@google.com, yamasani@google.com
+per-file RestrictionsSet.java = file:MULTIUSER_AND_ENTERPRISE_OWNERS
+per-file UserManager* = file:/MULTIUSER_OWNERS
+per-file UserRestriction* = file:MULTIUSER_AND_ENTERPRISE_OWNERS
+per-file UserSystemPackageInstaller* = file:/MULTIUSER_OWNERS
+per-file UserTypeDetails.java = file:/MULTIUSER_OWNERS
+per-file UserTypeFactory.java = file:/MULTIUSER_OWNERS
# security
per-file KeySetHandle.java = cbrubaker@google.com, nnk@google.com
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index f97a5ee..3d04b56 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -111,6 +111,7 @@
import android.os.incremental.IStorageHealthListener;
import android.os.incremental.IncrementalFileStorages;
import android.os.incremental.IncrementalManager;
+import android.os.incremental.PerUidReadTimeouts;
import android.os.incremental.StorageHealthCheckParams;
import android.os.storage.StorageManager;
import android.provider.Settings.Secure;
@@ -3006,7 +3007,7 @@
final boolean isInstallerShell = (mInstallerUid == Process.SHELL_UID);
if (isInstallerShell && isIncrementalInstallation() && mIncrementalFileStorages != null) {
if (!packageLite.debuggable && !packageLite.profilableByShell) {
- mIncrementalFileStorages.disableReadLogs();
+ mIncrementalFileStorages.disallowReadLogs();
}
}
}
@@ -3720,12 +3721,16 @@
};
if (!manualStartAndDestroy) {
+ final PerUidReadTimeouts[] perUidReadTimeouts = mPm.getPerUidReadTimeouts();
+
final StorageHealthCheckParams healthCheckParams = new StorageHealthCheckParams();
healthCheckParams.blockedTimeoutMs = INCREMENTAL_STORAGE_BLOCKED_TIMEOUT_MS;
healthCheckParams.unhealthyTimeoutMs = INCREMENTAL_STORAGE_UNHEALTHY_TIMEOUT_MS;
healthCheckParams.unhealthyMonitoringMs = INCREMENTAL_STORAGE_UNHEALTHY_MONITORING_MS;
+
final boolean systemDataLoader =
params.getComponentName().getPackageName() == SYSTEM_DATA_LOADER_PACKAGE;
+
final IStorageHealthListener healthListener = new IStorageHealthListener.Stub() {
@Override
public void onHealthStatus(int storageId, int status) {
@@ -3760,7 +3765,8 @@
try {
mIncrementalFileStorages = IncrementalFileStorages.initialize(mContext, stageDir,
- params, statusListener, healthCheckParams, healthListener, addedFiles);
+ params, statusListener, healthCheckParams, healthListener, addedFiles,
+ perUidReadTimeouts);
return false;
} catch (IOException e) {
throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE, e.getMessage(),
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 27008d8..4467b51 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -107,6 +107,7 @@
import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
+import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE;
import static com.android.internal.annotations.VisibleForTesting.Visibility;
import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE;
@@ -287,6 +288,7 @@
import android.os.incremental.IStorageHealthListener;
import android.os.incremental.IncrementalManager;
import android.os.incremental.IncrementalStorage;
+import android.os.incremental.PerUidReadTimeouts;
import android.os.incremental.StorageHealthCheckParams;
import android.os.storage.DiskInfo;
import android.os.storage.IStorageManager;
@@ -512,6 +514,7 @@
public static final boolean DEBUG_COMPRESSION = Build.IS_DEBUGGABLE;
public static final boolean DEBUG_CACHES = false;
public static final boolean TRACE_CACHES = false;
+ private static final boolean DEBUG_PER_UID_READ_TIMEOUTS = false;
// Debug output for dexopting. This is shared between PackageManagerService, OtaDexoptService
// and PackageDexOptimizer. All these classes have their own flag to allow switching a single
@@ -647,6 +650,24 @@
private static final long DEFAULT_ENABLE_ROLLBACK_TIMEOUT_MILLIS = 10 * 1000;
/**
+ * Default IncFs timeouts. Maximum values in IncFs is 1hr.
+ *
+ * <p>If flag value is empty, the default value will be assigned.
+ *
+ * Flag type: {@code String}
+ * Namespace: NAMESPACE_PACKAGE_MANAGER_SERVICE
+ */
+ private static final String PROPERTY_INCFS_DEFAULT_TIMEOUTS = "incfs_default_timeouts";
+
+ /**
+ * Known digesters with optional timeouts.
+ *
+ * Flag type: {@code String}
+ * Namespace: NAMESPACE_PACKAGE_MANAGER_SERVICE
+ */
+ private static final String PROPERTY_KNOWN_DIGESTERS_LIST = "known_digesters_list";
+
+ /**
* The default response for package verification timeout.
*
* This can be either PackageManager.VERIFICATION_ALLOW or
@@ -909,6 +930,11 @@
final private ArrayList<IPackageChangeObserver> mPackageChangeObservers =
new ArrayList<>();
+ // Cached parsed flag value. Invalidated on each flag change.
+ private PerUidReadTimeouts[] mPerUidReadTimeoutsCache;
+
+ private static final PerUidReadTimeouts[] EMPTY_PER_UID_READ_TIMEOUTS_ARRAY = {};
+
/**
* Unit tests will instantiate, extend and/or mock to mock dependencies / behaviors.
*
@@ -23738,6 +23764,17 @@
mInstallerService.restoreAndApplyStagedSessionIfNeeded();
mExistingPackages = null;
+
+ // Clear cache on flags changes.
+ DeviceConfig.addOnPropertiesChangedListener(
+ NAMESPACE_PACKAGE_MANAGER_SERVICE, FgThread.getExecutor(),
+ properties -> {
+ final Set<String> keyset = properties.getKeyset();
+ if (keyset.contains(PROPERTY_INCFS_DEFAULT_TIMEOUTS) || keyset.contains(
+ PROPERTY_KNOWN_DIGESTERS_LIST)) {
+ mPerUidReadTimeoutsCache = null;
+ }
+ });
}
public void waitForAppDataPrepared() {
@@ -23828,6 +23865,7 @@
pw.println(" v[erifiers]: print package verifier info");
pw.println(" d[omain-preferred-apps]: print domains preferred apps");
pw.println(" i[ntent-filter-verifiers]|ifv: print intent filter verifier info");
+ pw.println(" t[imeouts]: print read timeouts for known digesters");
pw.println(" version: print database version info");
pw.println(" write: write current settings now");
pw.println(" installs: details about install sessions");
@@ -23982,6 +24020,8 @@
dumpState.setDump(DumpState.DUMP_SERVICE_PERMISSIONS);
} else if ("known-packages".equals(cmd)) {
dumpState.setDump(DumpState.DUMP_KNOWN_PACKAGES);
+ } else if ("t".equals(cmd) || "timeouts".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_PER_UID_READ_TIMEOUTS);
} else if ("write".equals(cmd)) {
synchronized (mLock) {
writeSettingsLPrTEMP();
@@ -24380,6 +24420,25 @@
if (!checkin && dumpState.isDumping(DumpState.DUMP_APEX)) {
mApexManager.dump(pw, packageName);
}
+
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_PER_UID_READ_TIMEOUTS)
+ && packageName == null) {
+ pw.println();
+ pw.println("Per UID read timeouts:");
+ pw.println(" Default timeouts flag: " + getDefaultTimeouts());
+ pw.println(" Known digesters list flag: " + getKnownDigestersList());
+
+ PerUidReadTimeouts[] items = getPerUidReadTimeouts();
+ pw.println(" Timeouts (" + items.length + "):");
+ for (PerUidReadTimeouts item : items) {
+ pw.print(" (");
+ pw.print("uid=" + item.uid + ", ");
+ pw.print("minTimeUs=" + item.minTimeUs + ", ");
+ pw.print("minPendingTimeUs=" + item.minPendingTimeUs + ", ");
+ pw.print("maxPendingTimeUs=" + item.maxPendingTimeUs);
+ pw.println(")");
+ }
+ }
}
//TODO: b/111402650
@@ -27967,6 +28026,88 @@
SystemClock.sleep(durationMs);
}
}
+
+ private static String getDefaultTimeouts() {
+ return DeviceConfig.getString(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
+ PROPERTY_INCFS_DEFAULT_TIMEOUTS, "");
+ }
+
+ private static String getKnownDigestersList() {
+ return DeviceConfig.getString(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
+ PROPERTY_KNOWN_DIGESTERS_LIST, "");
+ }
+
+ /**
+ * Returns the array containing per-uid timeout configuration.
+ * This is derived from DeviceConfig flags.
+ */
+ public @NonNull PerUidReadTimeouts[] getPerUidReadTimeouts() {
+ PerUidReadTimeouts[] result = mPerUidReadTimeoutsCache;
+ if (result == null) {
+ result = parsePerUidReadTimeouts();
+ mPerUidReadTimeoutsCache = result;
+ }
+ return result;
+ }
+
+ private @NonNull PerUidReadTimeouts[] parsePerUidReadTimeouts() {
+ final String defaultTimeouts = getDefaultTimeouts();
+ final String knownDigestersList = getKnownDigestersList();
+ final List<PerPackageReadTimeouts> perPackageReadTimeouts =
+ PerPackageReadTimeouts.parseDigestersList(defaultTimeouts, knownDigestersList);
+
+ if (perPackageReadTimeouts.size() == 0) {
+ return EMPTY_PER_UID_READ_TIMEOUTS_ARRAY;
+ }
+
+ final int[] allUsers = mInjector.getUserManagerService().getUserIds();
+
+ List<PerUidReadTimeouts> result = new ArrayList<>(perPackageReadTimeouts.size());
+ synchronized (mLock) {
+ for (int i = 0, size = perPackageReadTimeouts.size(); i < size; ++i) {
+ final PerPackageReadTimeouts perPackage = perPackageReadTimeouts.get(i);
+ final PackageSetting ps = mSettings.mPackages.get(perPackage.packageName);
+ if (ps == null) {
+ if (DEBUG_PER_UID_READ_TIMEOUTS) {
+ Slog.i(TAG, "PerUidReadTimeouts: package not found = "
+ + perPackage.packageName);
+ }
+ continue;
+ }
+ final AndroidPackage pkg = ps.getPkg();
+ if (pkg.getLongVersionCode() < perPackage.versionCodes.minVersionCode
+ || pkg.getLongVersionCode() > perPackage.versionCodes.maxVersionCode) {
+ if (DEBUG_PER_UID_READ_TIMEOUTS) {
+ Slog.i(TAG, "PerUidReadTimeouts: version code is not in range = "
+ + perPackage.packageName + ":" + pkg.getLongVersionCode());
+ }
+ continue;
+ }
+ if (perPackage.sha256certificate != null
+ && !pkg.getSigningDetails().hasSha256Certificate(
+ perPackage.sha256certificate)) {
+ if (DEBUG_PER_UID_READ_TIMEOUTS) {
+ Slog.i(TAG, "PerUidReadTimeouts: invalid certificate = "
+ + perPackage.packageName + ":" + pkg.getLongVersionCode());
+ }
+ continue;
+ }
+ for (int userId : allUsers) {
+ if (!ps.getInstalled(userId)) {
+ continue;
+ }
+ final int uid = UserHandle.getUid(userId, ps.appId);
+ final PerUidReadTimeouts perUid = new PerUidReadTimeouts();
+ perUid.uid = uid;
+ perUid.minTimeUs = perPackage.timeouts.minTimeUs;
+ perUid.minPendingTimeUs = perPackage.timeouts.minPendingTimeUs;
+ perUid.maxPendingTimeUs = perPackage.timeouts.maxPendingTimeUs;
+ result.add(perUid);
+ }
+ }
+ }
+ return result.toArray(new PerUidReadTimeouts[result.size()]);
+ }
}
interface PackageSender {
diff --git a/services/core/java/com/android/server/pm/PerPackageReadTimeouts.java b/services/core/java/com/android/server/pm/PerPackageReadTimeouts.java
new file mode 100644
index 0000000..3b306a8
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PerPackageReadTimeouts.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.annotation.NonNull;;
+import android.text.TextUtils;
+
+import com.android.internal.util.HexDump;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+class PerPackageReadTimeouts {
+ static long tryParseLong(String str, long defaultValue) {
+ try {
+ return Long.parseLong(str);
+ } catch (NumberFormatException nfe) {
+ return defaultValue;
+ }
+ }
+
+ static byte[] tryParseSha256(String str) {
+ if (TextUtils.isEmpty(str)) {
+ return null;
+ }
+ try {
+ return HexDump.hexStringToByteArray(str);
+ } catch (RuntimeException e) {
+ return null;
+ }
+ }
+
+ static class Timeouts {
+ public final long minTimeUs;
+ public final long minPendingTimeUs;
+ public final long maxPendingTimeUs;
+
+ // 3600000000us == 1hr
+ public static final Timeouts DEFAULT = new Timeouts(3600000000L, 3600000000L, 3600000000L);
+
+ private Timeouts(long minTimeUs, long minPendingTimeUs, long maxPendingTimeUs) {
+ this.minTimeUs = minTimeUs;
+ this.minPendingTimeUs = minPendingTimeUs;
+ this.maxPendingTimeUs = maxPendingTimeUs;
+ }
+
+ static Timeouts parse(String timeouts) {
+ String[] splits = timeouts.split(":", 3);
+ if (splits.length != 3) {
+ return DEFAULT;
+ }
+ final long minTimeUs = tryParseLong(splits[0], DEFAULT.minTimeUs);
+ final long minPendingTimeUs = tryParseLong(splits[1], DEFAULT.minPendingTimeUs);
+ final long maxPendingTimeUs = tryParseLong(splits[2], DEFAULT.maxPendingTimeUs);
+ if (0 <= minTimeUs && minTimeUs <= minPendingTimeUs
+ && minPendingTimeUs <= maxPendingTimeUs) {
+ // validity check
+ return new Timeouts(minTimeUs, minPendingTimeUs, maxPendingTimeUs);
+ }
+ return DEFAULT;
+ }
+ }
+
+ static class VersionCodes {
+ public final long minVersionCode;
+ public final long maxVersionCode;
+
+ public static final VersionCodes ALL_VERSION_CODES = new VersionCodes(Long.MIN_VALUE,
+ Long.MAX_VALUE);
+
+ private VersionCodes(long minVersionCode, long maxVersionCode) {
+ this.minVersionCode = minVersionCode;
+ this.maxVersionCode = maxVersionCode;
+ }
+
+ static VersionCodes parse(String codes) {
+ if (TextUtils.isEmpty(codes)) {
+ return ALL_VERSION_CODES;
+ }
+ String[] splits = codes.split("-", 2);
+ switch (splits.length) {
+ case 1: {
+ // single version code
+ try {
+ final long versionCode = Long.parseLong(splits[0]);
+ return new VersionCodes(versionCode, versionCode);
+ } catch (NumberFormatException nfe) {
+ return ALL_VERSION_CODES;
+ }
+ }
+ case 2: {
+ final long minVersionCode = tryParseLong(splits[0],
+ ALL_VERSION_CODES.minVersionCode);
+ final long maxVersionCode = tryParseLong(splits[1],
+ ALL_VERSION_CODES.maxVersionCode);
+ if (minVersionCode <= maxVersionCode) {
+ return new VersionCodes(minVersionCode, maxVersionCode);
+ }
+ break;
+ }
+ }
+ return ALL_VERSION_CODES;
+ }
+ }
+
+ public final String packageName;
+ public final byte[] sha256certificate;
+ public final VersionCodes versionCodes;
+ public final Timeouts timeouts;
+
+ private PerPackageReadTimeouts(String packageName, byte[] sha256certificate,
+ VersionCodes versionCodes, Timeouts timeouts) {
+ this.packageName = packageName;
+ this.sha256certificate = sha256certificate;
+ this.versionCodes = versionCodes;
+ this.timeouts = timeouts;
+ }
+
+ @SuppressWarnings("fallthrough")
+ static PerPackageReadTimeouts parse(String timeoutsStr, VersionCodes defaultVersionCodes,
+ Timeouts defaultTimeouts) {
+ String packageName = null;
+ byte[] sha256certificate = null;
+ VersionCodes versionCodes = defaultVersionCodes;
+ Timeouts timeouts = defaultTimeouts;
+
+ final String[] splits = timeoutsStr.split(":", 4);
+ switch (splits.length) {
+ case 4:
+ timeouts = Timeouts.parse(splits[3]);
+ // fall through
+ case 3:
+ versionCodes = VersionCodes.parse(splits[2]);
+ // fall through
+ case 2:
+ sha256certificate = tryParseSha256(splits[1]);
+ // fall through
+ case 1:
+ packageName = splits[0];
+ break;
+ default:
+ return null;
+ }
+ if (TextUtils.isEmpty(packageName)) {
+ return null;
+ }
+
+ return new PerPackageReadTimeouts(packageName, sha256certificate, versionCodes,
+ timeouts);
+ }
+
+ static @NonNull List<PerPackageReadTimeouts> parseDigestersList(String defaultTimeoutsStr,
+ String knownDigestersList) {
+ if (TextUtils.isEmpty(knownDigestersList)) {
+ return Collections.emptyList();
+ }
+
+ final VersionCodes defaultVersionCodes = VersionCodes.ALL_VERSION_CODES;
+ final Timeouts defaultTimeouts = Timeouts.parse(defaultTimeoutsStr);
+
+ String[] packages = knownDigestersList.split(",");
+ List<PerPackageReadTimeouts> result = new ArrayList<>(packages.length);
+ for (int i = 0, size = packages.length; i < size; ++i) {
+ PerPackageReadTimeouts timeouts = PerPackageReadTimeouts.parse(packages[i],
+ defaultVersionCodes, defaultTimeouts);
+ if (timeouts != null) {
+ result.add(timeouts);
+ }
+ }
+ return result;
+ }
+}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 004c015..8f42289 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -4463,12 +4463,10 @@
final PermissionPolicyInternal permissionPolicyInternal = LocalServices.getService(
PermissionPolicyInternal.class);
- permissionPolicyInternal.setOnInitializedCallback(userId -> {
- // The SDK updated case is already handled when we run during the ctor.
- synchronized (mLock) {
- updateAllPermissions(StorageManager.UUID_PRIVATE_INTERNAL, false);
- }
- });
+ permissionPolicyInternal.setOnInitializedCallback(userId ->
+ // The SDK updated case is already handled when we run during the ctor.
+ updateAllPermissions(StorageManager.UUID_PRIVATE_INTERNAL, false)
+ );
mSystemReady = true;
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
index 35c9f9a..3a08ddc 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -25,6 +25,8 @@
import android.content.rollback.PackageRollbackInfo.RestoreInfo;
import android.content.rollback.RollbackInfo;
import android.os.UserHandle;
+import android.system.ErrnoException;
+import android.system.Os;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseIntArray;
@@ -237,8 +239,19 @@
targetDir.mkdirs();
File targetFile = new File(targetDir, sourceFile.getName());
- // TODO: Copy by hard link instead to save on cpu and storage space?
- Files.copy(sourceFile.toPath(), targetFile.toPath());
+ try {
+ // Create a hard link to avoid copy
+ // TODO(b/168562373)
+ // Linking between non-encrypted and encrypted is not supported and we have
+ // encrypted /data/rollback and non-encrypted /data/apex/active. For now this works
+ // because we happen to store encrypted files under /data/apex/active which is no
+ // longer the case when compressed apex rolls out. We have to handle this case in
+ // order not to fall back to copy.
+ Os.link(sourceFile.getAbsolutePath(), targetFile.getAbsolutePath());
+ } catch (ErrnoException ignore) {
+ // Fall back to copy if hardlink can't be created
+ Files.copy(sourceFile.toPath(), targetFile.toPath());
+ }
}
/**
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 9088d7b..d656617 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -138,7 +138,6 @@
import com.android.internal.os.BatterySipper;
import com.android.internal.os.BatteryStatsHelper;
import com.android.internal.os.BinderCallsStats.ExportedCallStat;
-import com.android.internal.os.KernelCpuSpeedReader;
import com.android.internal.os.KernelCpuThreadReader;
import com.android.internal.os.KernelCpuThreadReaderDiff;
import com.android.internal.os.KernelCpuThreadReaderSettingsObserver;
@@ -301,8 +300,6 @@
@GuardedBy("mDiskIoLock")
private StoragedUidIoStatsReader mStoragedUidIoStatsReader;
- @GuardedBy("mCpuTimePerFreqLock")
- private KernelCpuSpeedReader[] mKernelCpuSpeedReaders;
// Disables throttler on CPU time readers.
@GuardedBy("mCpuTimePerUidLock")
private KernelCpuUidUserSysTimeReader mCpuUidUserSysTimeReader;
@@ -353,7 +350,6 @@
private final Object mDataBytesTransferLock = new Object();
private final Object mBluetoothBytesTransferLock = new Object();
private final Object mKernelWakelockLock = new Object();
- private final Object mCpuTimePerFreqLock = new Object();
private final Object mCpuTimePerUidLock = new Object();
private final Object mCpuTimePerUidFreqLock = new Object();
private final Object mCpuActiveTimeLock = new Object();
@@ -442,10 +438,6 @@
synchronized (mKernelWakelockLock) {
return pullKernelWakelockLocked(atomTag, data);
}
- case FrameworkStatsLog.CPU_TIME_PER_FREQ:
- synchronized (mCpuTimePerFreqLock) {
- return pullCpuTimePerFreqLocked(atomTag, data);
- }
case FrameworkStatsLog.CPU_TIME_PER_UID:
synchronized (mCpuTimePerUidLock) {
return pullCpuTimePerUidLocked(atomTag, data);
@@ -722,18 +714,6 @@
mKernelWakelockReader = new KernelWakelockReader();
mTmpWakelockStats = new KernelWakelockStats();
- // Initialize state for CPU_TIME_PER_FREQ atom
- PowerProfile powerProfile = new PowerProfile(mContext);
- final int numClusters = powerProfile.getNumCpuClusters();
- mKernelCpuSpeedReaders = new KernelCpuSpeedReader[numClusters];
- int firstCpuOfCluster = 0;
- for (int i = 0; i < numClusters; i++) {
- final int numSpeedSteps = powerProfile.getNumSpeedStepsInCpuCluster(i);
- mKernelCpuSpeedReaders[i] = new KernelCpuSpeedReader(firstCpuOfCluster,
- numSpeedSteps);
- firstCpuOfCluster += powerProfile.getNumCoresInCpuCluster(i);
- }
-
// Used for CPU_TIME_PER_THREAD_FREQ
mKernelCpuThreadReader =
KernelCpuThreadReaderSettingsObserver.getSettingsModifiedReader(mContext);
@@ -793,7 +773,6 @@
mStatsCallbackImpl = new StatsPullAtomCallbackImpl();
registerBluetoothBytesTransfer();
registerKernelWakelock();
- registerCpuTimePerFreq();
registerCpuTimePerUid();
registerCpuCyclesPerUidCluster();
registerCpuTimePerUidFreq();
@@ -1465,32 +1444,6 @@
return StatsManager.PULL_SUCCESS;
}
- private void registerCpuTimePerFreq() {
- int tagId = FrameworkStatsLog.CPU_TIME_PER_FREQ;
- PullAtomMetadata metadata = new PullAtomMetadata.Builder()
- .setAdditiveFields(new int[] {3})
- .build();
- mStatsManager.setPullAtomCallback(
- tagId,
- metadata,
- DIRECT_EXECUTOR,
- mStatsCallbackImpl
- );
- }
-
- int pullCpuTimePerFreqLocked(int atomTag, List<StatsEvent> pulledData) {
- for (int cluster = 0; cluster < mKernelCpuSpeedReaders.length; cluster++) {
- long[] clusterTimeMs = mKernelCpuSpeedReaders[cluster].readAbsolute();
- if (clusterTimeMs != null) {
- for (int speed = clusterTimeMs.length - 1; speed >= 0; --speed) {
- pulledData.add(FrameworkStatsLog.buildStatsEvent(
- atomTag, cluster, speed, clusterTimeMs[speed]));
- }
- }
- }
- return StatsManager.PULL_SUCCESS;
- }
-
private void registerCpuTimePerUid() {
int tagId = FrameworkStatsLog.CPU_TIME_PER_UID;
PullAtomMetadata metadata = new PullAtomMetadata.Builder()
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index 865571e..d0c6323 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -18,6 +18,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
import android.app.time.ITimeZoneDetectorListener;
import android.app.time.TimeZoneCapabilitiesAndConfig;
import android.app.time.TimeZoneConfiguration;
@@ -26,12 +28,14 @@
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.content.Context;
import android.location.LocationManager;
+import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.SystemProperties;
+import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
import android.util.Slog;
@@ -163,9 +167,13 @@
@Override
@NonNull
public TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig() {
+ int userId = mCallerIdentityInjector.getCallingUserId();
+ return getCapabilitiesAndConfig(userId);
+ }
+
+ TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig(@UserIdInt int userId) {
enforceManageTimeZoneDetectorPermission();
- int userId = mCallerIdentityInjector.getCallingUserId();
final long token = mCallerIdentityInjector.clearCallingIdentity();
try {
ConfigurationInternal configurationInternal =
@@ -178,13 +186,22 @@
@Override
public boolean updateConfiguration(@NonNull TimeZoneConfiguration configuration) {
+ int callingUserId = mCallerIdentityInjector.getCallingUserId();
+ return updateConfiguration(callingUserId, configuration);
+ }
+
+ boolean updateConfiguration(
+ @UserIdInt int userId, @NonNull TimeZoneConfiguration configuration) {
+ userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ userId, false, false, "updateConfiguration", null);
+
enforceManageTimeZoneDetectorPermission();
+
Objects.requireNonNull(configuration);
- int callingUserId = mCallerIdentityInjector.getCallingUserId();
final long token = mCallerIdentityInjector.clearCallingIdentity();
try {
- return mTimeZoneDetectorStrategy.updateConfiguration(callingUserId, configuration);
+ return mTimeZoneDetectorStrategy.updateConfiguration(userId, configuration);
} finally {
mCallerIdentityInjector.restoreCallingIdentity(token);
}
@@ -318,11 +335,17 @@
return isGeoLocationTimeZoneDetectionEnabled(mContext);
}
- boolean isLocationEnabled() {
+ boolean isLocationEnabled(@UserIdInt int userId) {
enforceManageTimeZoneDetectorPermission();
- return mContext.getSystemService(LocationManager.class)
- .isLocationEnabledForUser(mContext.getUser());
+ final long token = mCallerIdentityInjector.clearCallingIdentity();
+ try {
+ UserHandle user = UserHandle.of(userId);
+ LocationManager locationManager = mContext.getSystemService(LocationManager.class);
+ return locationManager.isLocationEnabledForUser(user);
+ } finally {
+ mCallerIdentityInjector.restoreCallingIdentity(token);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
index b263030..e965f55 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
@@ -29,6 +29,7 @@
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.os.ShellCommand;
+import android.os.UserHandle;
import java.io.PrintWriter;
import java.util.function.Consumer;
@@ -76,7 +77,8 @@
private int runIsAutoDetectionEnabled() {
final PrintWriter pw = getOutPrintWriter();
- boolean enabled = mInterface.getCapabilitiesAndConfig()
+ int userId = UserHandle.USER_CURRENT;
+ boolean enabled = mInterface.getCapabilitiesAndConfig(userId)
.getConfiguration()
.isAutoDetectionEnabled();
pw.println(enabled);
@@ -92,14 +94,16 @@
private int runIsLocationEnabled() {
final PrintWriter pw = getOutPrintWriter();
- boolean enabled = mInterface.isLocationEnabled();
+ int userId = UserHandle.USER_CURRENT;
+ boolean enabled = mInterface.isLocationEnabled(userId);
pw.println(enabled);
return 0;
}
private int runIsGeoDetectionEnabled() {
final PrintWriter pw = getOutPrintWriter();
- boolean enabled = mInterface.getCapabilitiesAndConfig()
+ int userId = UserHandle.USER_CURRENT;
+ boolean enabled = mInterface.getCapabilitiesAndConfig(userId)
.getConfiguration()
.isGeoDetectionEnabled();
pw.println(enabled);
@@ -108,18 +112,20 @@
private int runSetAutoDetectionEnabled() {
boolean enabled = Boolean.parseBoolean(getNextArgRequired());
+ int userId = UserHandle.USER_CURRENT;
TimeZoneConfiguration configuration = new TimeZoneConfiguration.Builder()
.setAutoDetectionEnabled(enabled)
.build();
- return mInterface.updateConfiguration(configuration) ? 0 : 1;
+ return mInterface.updateConfiguration(userId, configuration) ? 0 : 1;
}
private int runSetGeoDetectionEnabled() {
boolean enabled = Boolean.parseBoolean(getNextArgRequired());
+ int userId = UserHandle.USER_CURRENT;
TimeZoneConfiguration configuration = new TimeZoneConfiguration.Builder()
.setGeoDetectionEnabled(enabled)
.build();
- return mInterface.updateConfiguration(configuration) ? 0 : 1;
+ return mInterface.updateConfiguration(userId, configuration) ? 0 : 1;
}
private int runSuggestGeolocationTimeZone() {
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index b084787..03cf021 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -174,6 +174,10 @@
boolean allDrawn() {
return mAssociatedTransitionInfo != null && mAssociatedTransitionInfo.allDrawn();
}
+
+ boolean contains(ActivityRecord r) {
+ return mAssociatedTransitionInfo != null && mAssociatedTransitionInfo.contains(r);
+ }
}
/** The information created when an activity is confirmed to be launched. */
@@ -793,6 +797,7 @@
stopLaunchTrace(info);
if (abort) {
+ mSupervisor.stopWaitingForActivityVisible(info.mLastLaunchedActivity);
launchObserverNotifyActivityLaunchCancelled(info);
} else {
if (info.isInterestingToLoggerAndObserver()) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 324e3ac..a9c5474 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5435,7 +5435,7 @@
final TransitionInfoSnapshot info = mTaskSupervisor
.getActivityMetricsLogger().logAppTransitionReportedDrawn(this, restoredFromBundle);
if (info != null) {
- mTaskSupervisor.reportActivityLaunchedLocked(false /* timeout */, this,
+ mTaskSupervisor.reportActivityLaunched(false /* timeout */, this,
info.windowsFullyDrawnDelayMs, info.getLaunchState());
}
}
@@ -5476,9 +5476,8 @@
// so there is no valid info. But if it is the current top activity (e.g. sleeping), the
// invalid state is still reported to make sure the waiting result is notified.
if (validInfo || this == getDisplayArea().topRunningActivity()) {
- mTaskSupervisor.reportActivityLaunchedLocked(false /* timeout */, this,
+ mTaskSupervisor.reportActivityLaunched(false /* timeout */, this,
windowsDrawnDelayMs, launchState);
- mTaskSupervisor.stopWaitingForActivityVisible(this, windowsDrawnDelayMs, launchState);
}
finishLaunchTickingLocked();
if (task != null) {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 375c3e1..7a4bcb1 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -710,8 +710,12 @@
// WaitResult.
mSupervisor.getActivityMetricsLogger().notifyActivityLaunched(launchingState, res,
mLastStartActivityRecord, originalOptions);
- return getExternalResult(mRequest.waitResult == null ? res
- : waitForResult(res, mLastStartActivityRecord));
+ if (mRequest.waitResult != null) {
+ mRequest.waitResult.result = res;
+ res = waitResultIfNeeded(mRequest.waitResult, mLastStartActivityRecord,
+ launchingState);
+ }
+ return getExternalResult(res);
}
} finally {
onExecutionComplete();
@@ -796,48 +800,21 @@
/**
* Wait for activity launch completes.
*/
- private int waitForResult(int res, ActivityRecord r) {
- mRequest.waitResult.result = res;
- switch(res) {
- case START_SUCCESS: {
- mSupervisor.mWaitingActivityLaunched.add(mRequest.waitResult);
- do {
- try {
- mService.mGlobalLock.wait();
- } catch (InterruptedException e) {
- }
- } while (mRequest.waitResult.result != START_TASK_TO_FRONT
- && !mRequest.waitResult.timeout && mRequest.waitResult.who == null);
- if (mRequest.waitResult.result == START_TASK_TO_FRONT) {
- res = START_TASK_TO_FRONT;
- }
- break;
- }
- case START_DELIVERED_TO_TOP: {
- mRequest.waitResult.timeout = false;
- mRequest.waitResult.who = r.mActivityComponent;
- mRequest.waitResult.totalTime = 0;
- break;
- }
- case START_TASK_TO_FRONT: {
- // ActivityRecord may represent a different activity, but it should not be
- // in the resumed state.
- if (r.nowVisible && r.isState(RESUMED)) {
- mRequest.waitResult.timeout = false;
- mRequest.waitResult.who = r.mActivityComponent;
- mRequest.waitResult.totalTime = 0;
- } else {
- mSupervisor.waitActivityVisible(r.mActivityComponent, mRequest.waitResult);
- // Note: the timeout variable is not currently not ever set.
- do {
- try {
- mService.mGlobalLock.wait();
- } catch (InterruptedException e) {
- }
- } while (!mRequest.waitResult.timeout && mRequest.waitResult.who == null);
- }
- break;
- }
+ private int waitResultIfNeeded(WaitResult waitResult, ActivityRecord r,
+ LaunchingState launchingState) {
+ final int res = waitResult.result;
+ if (res == START_DELIVERED_TO_TOP
+ || (res == START_TASK_TO_FRONT && r.nowVisible && r.isState(RESUMED))) {
+ // The activity should already be visible, so nothing to wait.
+ waitResult.timeout = false;
+ waitResult.who = r.mActivityComponent;
+ waitResult.totalTime = 0;
+ return res;
+ }
+ mSupervisor.waitActivityVisibleOrLaunched(waitResult, r, launchingState);
+ if (res == START_SUCCESS && waitResult.result == START_TASK_TO_FRONT) {
+ // A trampoline activity is launched and it brings another existing activity to front.
+ return START_TASK_TO_FRONT;
}
return res;
}
@@ -1669,7 +1646,7 @@
if (startedActivityStack != null && startedActivityStack.isAttached()
&& !startedActivityStack.hasActivity()
&& !startedActivityStack.isActivityTypeHome()) {
- startedActivityStack.removeIfPossible();
+ startedActivityStack.removeIfPossible("handleStartResult");
startedActivityStack = null;
}
return startedActivityStack;
@@ -1857,7 +1834,7 @@
return top.getTask();
} else {
// Remove the stack if no activity in the stack.
- stack.removeIfPossible();
+ stack.removeIfPossible("computeTargetTask");
}
}
return null;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 9ffedde..109ea09 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -285,6 +285,14 @@
public abstract void enforceCallerIsRecentsOrHasPermission(String permission, String func);
/**
+ * Returns true if the app can close system dialogs. Otherwise it either throws a {@link
+ * SecurityException} or returns false with a logcat message depending on whether the app
+ * targets SDK level {@link android.os.Build.VERSION_CODES#S} or not.
+ */
+ public abstract boolean checkCanCloseSystemDialogs(int pid, int uid,
+ @Nullable String packageName);
+
+ /**
* Called after the voice interaction service has changed.
*/
public abstract void notifyActiveVoiceInteractionServiceChanged(ComponentName component);
@@ -563,8 +571,13 @@
*/
public abstract void setDeviceOwnerUid(int uid);
- /** Set all associated companion app that belongs to an userId. */
- public abstract void setCompanionAppPackages(int userId, Set<String> companionAppPackages);
+ /**
+ * Set all associated companion app that belongs to a userId.
+ * @param userId
+ * @param companionAppUids ActivityTaskManager will take ownership of this Set, the caller
+ * shouldn't touch the Set after calling this interface.
+ */
+ public abstract void setCompanionAppUids(int userId, Set<Integer> companionAppUids);
/**
* @param packageName The package to check
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 461bbfb..039f22d 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -147,6 +147,7 @@
import android.app.admin.DevicePolicyCache;
import android.app.assist.AssistContent;
import android.app.assist.AssistStructure;
+import android.app.compat.CompatChanges;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
@@ -2900,6 +2901,86 @@
}
}
+ /**
+ * Returns true if the app can close system dialogs. Otherwise it either throws a {@link
+ * SecurityException} or returns false with a logcat message depending on whether the app
+ * targets SDK level {@link android.os.Build.VERSION_CODES#S} or not.
+ */
+ private boolean checkCanCloseSystemDialogs(int pid, int uid, @Nullable String packageName) {
+ final WindowProcessController process;
+ synchronized (mGlobalLock) {
+ process = mProcessMap.getProcess(pid);
+ }
+ if (packageName == null && process != null) {
+ // WindowProcessController.mInfo is final, so after the synchronized memory barrier
+ // above, process.mInfo can't change. As for reading mInfo.packageName,
+ // WindowProcessController doesn't own the ApplicationInfo object referenced by mInfo.
+ // ProcessRecord for example also holds a reference to that object, so protecting access
+ // to packageName with the WM lock would not be enough as we'd also need to synchronize
+ // on the AM lock if we are worried about races, but we can't synchronize on AM lock
+ // here. Hence, since this is only used for logging, we don't synchronize here.
+ packageName = process.mInfo.packageName;
+ }
+ String caller = "(pid=" + pid + ", uid=" + uid + ")";
+ if (packageName != null) {
+ caller = packageName + " " + caller;
+ }
+ if (!canCloseSystemDialogs(pid, uid, process)) {
+ // The app can't close system dialogs, throw only if it targets S+
+ if (CompatChanges.isChangeEnabled(
+ ActivityManager.LOCK_DOWN_CLOSE_SYSTEM_DIALOGS, uid)) {
+ throw new SecurityException(
+ "Permission Denial: " + Intent.ACTION_CLOSE_SYSTEM_DIALOGS
+ + " broadcast from " + caller + " requires "
+ + Manifest.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS + ".");
+ } else if (CompatChanges.isChangeEnabled(
+ ActivityManager.DROP_CLOSE_SYSTEM_DIALOGS, uid)) {
+ Slog.e(TAG,
+ "Permission Denial: " + Intent.ACTION_CLOSE_SYSTEM_DIALOGS
+ + " broadcast from " + caller + " requires "
+ + Manifest.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS
+ + ", dropping broadcast.");
+ return false;
+ } else {
+ Slog.w(TAG, Intent.ACTION_CLOSE_SYSTEM_DIALOGS
+ + " broadcast from " + caller + " will require "
+ + Manifest.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS
+ + " in future builds.");
+ return true;
+ }
+ }
+ return true;
+ }
+
+ private boolean canCloseSystemDialogs(int pid, int uid,
+ @Nullable WindowProcessController process) {
+ if (checkPermission(Manifest.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS, pid, uid)
+ == PERMISSION_GRANTED) {
+ return true;
+ }
+ if (process != null) {
+ // Check if the instrumentation of the process has the permission. This covers the
+ // usual test started from the shell (which has the permission) case. This is needed
+ // for apps targeting SDK level < S but we are also allowing for targetSdk S+ as a
+ // convenience to avoid breaking a bunch of existing tests and asking them to adopt
+ // shell permissions to do this.
+ // Note that these getters all read from volatile fields in WindowProcessController, so
+ // no need to lock.
+ int sourceUid = process.getInstrumentationSourceUid();
+ if (process.isInstrumenting() && sourceUid != -1 && checkPermission(
+ Manifest.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS, -1, sourceUid)
+ == PERMISSION_GRANTED) {
+ return true;
+ }
+ // This is the notification trampoline use-case for example, where apps use Intent.ACSD
+ // to close the shade prior to starting an activity.
+ if (process.canCloseSystemDialogsByToken()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
static void enforceTaskPermission(String func) {
if (checkCallingPermission(MANAGE_ACTIVITY_TASKS) == PackageManager.PERMISSION_GRANTED) {
return;
@@ -5150,6 +5231,12 @@
}
@Override
+ public boolean checkCanCloseSystemDialogs(int pid, int uid, @Nullable String packageName) {
+ return ActivityTaskManagerService.this.checkCanCloseSystemDialogs(pid, uid,
+ packageName);
+ }
+
+ @Override
public void notifyActiveVoiceInteractionServiceChanged(ComponentName component) {
synchronized (mGlobalLock) {
mActiveVoiceInteractionServiceComponent = component;
@@ -5649,9 +5736,12 @@
@Override
public void closeSystemDialogs(String reason) {
enforceNotIsolatedCaller("closeSystemDialogs");
-
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
+ if (!checkCanCloseSystemDialogs(pid, uid, null)) {
+ return;
+ }
+
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
@@ -6272,17 +6362,9 @@
}
@Override
- public void setCompanionAppPackages(int userId, Set<String> companionAppPackages) {
- // Translate package names into UIDs
- final Set<Integer> result = new HashSet<>();
- for (String pkg : companionAppPackages) {
- final int uid = getPackageManagerInternalLocked().getPackageUid(pkg, 0, userId);
- if (uid >= 0) {
- result.add(uid);
- }
- }
+ public void setCompanionAppUids(int userId, Set<Integer> companionAppUids) {
synchronized (mGlobalLock) {
- mCompanionAppUidsMap.put(userId, result);
+ mCompanionAppUidsMap.put(userId, companionAppUids);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 73a6efd..599bf37 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -266,11 +266,8 @@
*/
private final SparseIntArray mCurTaskIdForUser = new SparseIntArray(20);
- /** List of processes waiting to find out when a specific activity becomes visible. */
- private final ArrayList<WaitInfo> mWaitingForActivityVisible = new ArrayList<>();
-
- /** List of processes waiting to find out about the next launched activity. */
- final ArrayList<WaitResult> mWaitingActivityLaunched = new ArrayList<>();
+ /** List of requests waiting for the target activity to be launched or visible. */
+ private final ArrayList<WaitInfo> mWaitingActivityLaunched = new ArrayList<>();
/** List of activities that are ready to be stopped, but waiting for the next activity to
* settle down before doing so. */
@@ -552,9 +549,21 @@
return candidateTaskId;
}
- void waitActivityVisible(ComponentName name, WaitResult result) {
- final WaitInfo waitInfo = new WaitInfo(name, result);
- mWaitingForActivityVisible.add(waitInfo);
+ void waitActivityVisibleOrLaunched(WaitResult w, ActivityRecord r,
+ LaunchingState launchingState) {
+ if (w.result != ActivityManager.START_TASK_TO_FRONT
+ && w.result != ActivityManager.START_SUCCESS) {
+ // Not a result code that can make activity visible or launched.
+ return;
+ }
+ final WaitInfo waitInfo = new WaitInfo(w, r.mActivityComponent, launchingState);
+ mWaitingActivityLaunched.add(waitInfo);
+ do {
+ try {
+ mService.mGlobalLock.wait();
+ } catch (InterruptedException ignored) {
+ }
+ } while (mWaitingActivityLaunched.contains(waitInfo));
}
void cleanupActivity(ActivityRecord r) {
@@ -568,23 +577,25 @@
/** There is no valid launch time, just stop waiting. */
void stopWaitingForActivityVisible(ActivityRecord r) {
- stopWaitingForActivityVisible(r, WaitResult.INVALID_DELAY, WaitResult.LAUNCH_STATE_UNKNOWN);
+ reportActivityLaunched(false /* timeout */, r, WaitResult.INVALID_DELAY,
+ WaitResult.LAUNCH_STATE_UNKNOWN);
}
- void stopWaitingForActivityVisible(ActivityRecord r, long totalTime,
+ void reportActivityLaunched(boolean timeout, ActivityRecord r, long totalTime,
@WaitResult.LaunchState int launchState) {
boolean changed = false;
- for (int i = mWaitingForActivityVisible.size() - 1; i >= 0; --i) {
- final WaitInfo w = mWaitingForActivityVisible.get(i);
- if (w.matches(r.mActivityComponent)) {
- final WaitResult result = w.getResult();
- changed = true;
- result.timeout = false;
- result.who = w.getComponent();
- result.totalTime = totalTime;
- result.launchState = launchState;
- mWaitingForActivityVisible.remove(w);
+ for (int i = mWaitingActivityLaunched.size() - 1; i >= 0; i--) {
+ final WaitInfo info = mWaitingActivityLaunched.get(i);
+ if (!info.matches(r)) {
+ continue;
}
+ final WaitResult w = info.mResult;
+ w.timeout = timeout;
+ w.who = r.mActivityComponent;
+ w.totalTime = totalTime;
+ w.launchState = launchState;
+ mWaitingActivityLaunched.remove(i);
+ changed = true;
}
if (changed) {
mService.mGlobalLock.notifyAll();
@@ -603,38 +614,18 @@
boolean changed = false;
for (int i = mWaitingActivityLaunched.size() - 1; i >= 0; i--) {
- WaitResult w = mWaitingActivityLaunched.remove(i);
- if (w.who == null) {
- changed = true;
- w.result = result;
-
+ final WaitInfo info = mWaitingActivityLaunched.get(i);
+ if (!info.matches(r)) {
+ continue;
+ }
+ final WaitResult w = info.mResult;
+ w.result = result;
+ if (result == START_DELIVERED_TO_TOP) {
// Unlike START_TASK_TO_FRONT, When an intent is delivered to top, there
// will be no followup launch signals. Assign the result and launched component.
- if (result == START_DELIVERED_TO_TOP) {
- w.who = r.mActivityComponent;
- }
- }
- }
-
- if (changed) {
- mService.mGlobalLock.notifyAll();
- }
- }
-
- void reportActivityLaunchedLocked(boolean timeout, ActivityRecord r, long totalTime,
- @WaitResult.LaunchState int launchState) {
- boolean changed = false;
- for (int i = mWaitingActivityLaunched.size() - 1; i >= 0; i--) {
- WaitResult w = mWaitingActivityLaunched.remove(i);
- if (w.who == null) {
+ w.who = r.mActivityComponent;
+ mWaitingActivityLaunched.remove(i);
changed = true;
- w.timeout = timeout;
- if (r != null) {
- w.who = new ComponentName(r.info.packageName, r.info.name);
- }
- w.totalTime = totalTime;
- w.launchState = launchState;
- // Do not modify w.result.
}
}
if (changed) {
@@ -1295,8 +1286,7 @@
mHandler.removeMessages(IDLE_TIMEOUT_MSG, r);
r.finishLaunchTickingLocked();
if (fromTimeout) {
- reportActivityLaunchedLocked(fromTimeout, r, INVALID_DELAY,
- -1 /* launchState */);
+ reportActivityLaunched(fromTimeout, r, INVALID_DELAY, -1 /* launchState */);
}
// This is a hack to semi-deal with a race condition
@@ -1940,14 +1930,14 @@
pw.println("mCurTaskIdForUser=" + mCurTaskIdForUser);
pw.println(prefix + "mUserRootTaskInFront=" + mRootWindowContainer.mUserRootTaskInFront);
pw.println(prefix + "mVisibilityTransactionDepth=" + mVisibilityTransactionDepth);
- if (!mWaitingForActivityVisible.isEmpty()) {
- pw.println(prefix + "mWaitingForActivityVisible=");
- for (int i = 0; i < mWaitingForActivityVisible.size(); ++i) {
- pw.print(prefix + prefix); mWaitingForActivityVisible.get(i).dump(pw, prefix);
- }
- }
pw.print(prefix); pw.print("isHomeRecentsComponent=");
pw.println(mRecentTasks.isRecentsComponentHomeActivity(mRootWindowContainer.mCurrentUser));
+ if (!mWaitingActivityLaunched.isEmpty()) {
+ pw.println(prefix + "mWaitingActivityLaunched=");
+ for (int i = mWaitingActivityLaunched.size() - 1; i >= 0; i--) {
+ mWaitingActivityLaunched.get(i).dump(pw, prefix + " ");
+ }
+ }
pw.println();
}
@@ -2604,32 +2594,30 @@
/**
* Internal container to store a match qualifier alongside a WaitResult.
*/
- static class WaitInfo {
- private final ComponentName mTargetComponent;
- private final WaitResult mResult;
+ private static class WaitInfo {
+ final WaitResult mResult;
+ final ComponentName mTargetComponent;
+ /**
+ * The target component may not be the final drawn activity. The launching state is managed
+ * by {@link ActivityMetricsLogger} that can track consecutive launching sequence.
+ */
+ final LaunchingState mLaunchingState;
- WaitInfo(ComponentName targetComponent, WaitResult result) {
- this.mTargetComponent = targetComponent;
- this.mResult = result;
+ WaitInfo(WaitResult result, ComponentName component, LaunchingState launchingState) {
+ mResult = result;
+ mTargetComponent = component;
+ mLaunchingState = launchingState;
}
- public boolean matches(ComponentName targetComponent) {
- return mTargetComponent == null || mTargetComponent.equals(targetComponent);
+ boolean matches(ActivityRecord r) {
+ return mTargetComponent.equals(r.mActivityComponent) || mLaunchingState.contains(r);
}
- public WaitResult getResult() {
- return mResult;
- }
-
- public ComponentName getComponent() {
- return mTargetComponent;
- }
-
- public void dump(PrintWriter pw, String prefix) {
+ void dump(PrintWriter pw, String prefix) {
pw.println(prefix + "WaitInfo:");
pw.println(prefix + " mTargetComponent=" + mTargetComponent);
pw.println(prefix + " mResult=");
- mResult.dump(pw, prefix);
+ mResult.dump(pw, prefix + " ");
}
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index c3a7542..4494d99 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5490,7 +5490,7 @@
if (!hasNonEmptyHomeRootTask && getRootTaskCount() > 0) {
// Release this display if only empty home root task(s) are left. This display will be
// released along with the root task(s) removal.
- forAllRootTasks(Task::removeIfPossible);
+ forAllRootTasks(t -> {t.removeIfPossible("releaseSelfIfNeeded");});
} else if (getTopRootTask() == null) {
removeIfPossible();
mRootWindowContainer.mTaskSupervisor
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 1c41978..a2a6985 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -916,21 +916,26 @@
mTaskSupervisor.mRecentTasks.remove(this);
}
- removeIfPossible();
+ removeIfPossible("cleanUpResourcesForDestroy");
}
@VisibleForTesting
@Override
void removeIfPossible() {
+ removeIfPossible("removeTaskIfPossible");
+ }
+
+ void removeIfPossible(String reason) {
final boolean isRootTask = isRootTask();
if (!isRootTask) {
mAtmService.getLockTaskController().clearLockedTask(this);
}
if (shouldDeferRemoval()) {
- if (DEBUG_ROOT_TASK) Slog.i(TAG, "removeTask: deferring removing taskId=" + mTaskId);
+ if (DEBUG_ROOT_TASK) Slog.i(TAG,
+ "removeTask:" + reason + " deferring removing taskId=" + mTaskId);
return;
}
- removeImmediately();
+ removeImmediately(reason);
if (isLeafTask()) {
mAtmService.getTaskChangeNotificationController().notifyTaskRemoved(mTaskId);
@@ -1774,8 +1779,8 @@
getRootTask().removeChild(this, reason);
}
EventLogTags.writeWmTaskRemoved(mTaskId,
- "removeChild: last r=" + r + " in t=" + this);
- removeIfPossible();
+ "removeChild:" + reason + " last r=" + r + " in t=" + this);
+ removeIfPossible(reason);
}
}
@@ -1818,7 +1823,7 @@
if (r.finishing) return;
// Task was restored from persistent storage.
r.takeFromHistory();
- removeChild(r);
+ removeChild(r, reason);
});
} else {
forAllActivities((r) -> {
@@ -3214,8 +3219,12 @@
@Override
void removeImmediately() {
- if (DEBUG_ROOT_TASK) Slog.i(TAG, "removeTask: removing taskId=" + mTaskId);
- EventLogTags.writeWmTaskRemoved(mTaskId, "removeTask");
+ removeImmediately("removeTask");
+ }
+
+ void removeImmediately(String reason) {
+ if (DEBUG_ROOT_TASK) Slog.i(TAG, "removeTask:" + reason + " removing taskId=" + mTaskId);
+ EventLogTags.writeWmTaskRemoved(mTaskId, reason);
// If applicable let the TaskOrganizer know the Task is vanishing.
setTaskOrganizer(null);
@@ -4986,7 +4995,7 @@
mTaskAppearedSent = false;
setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, false /* set */);
if (mCreatedByOrganizer) {
- removeImmediately();
+ removeImmediately("setTaskOrganizer");
}
}
@@ -5104,7 +5113,9 @@
}
void onPictureInPictureParamsChanged() {
- dispatchTaskInfoChangedIfNeeded(true /* force */);
+ if (inPinnedWindowingMode()) {
+ dispatchTaskInfoChangedIfNeeded(true /* force */);
+ }
}
/**
@@ -5436,14 +5447,6 @@
r.completeResumeLocked();
}
- private void clearLaunchTime(ActivityRecord r) {
- // Make sure that there is no activity waiting for this to launch.
- if (!mTaskSupervisor.mWaitingActivityLaunched.isEmpty()) {
- mTaskSupervisor.removeIdleTimeoutForActivity(r);
- mTaskSupervisor.scheduleIdleTimeout(r);
- }
- }
-
void awakeFromSleepingLocked() {
if (!isLeafTask()) {
forAllLeafTasks((task) -> task.awakeFromSleepingLocked(),
@@ -5586,7 +5589,6 @@
mLastNoHistoryActivity = prev.isNoHistory() ? prev : null;
prev.setState(PAUSING, "startPausingLocked");
prev.getTask().touchActiveTime();
- clearLaunchTime(prev);
mAtmService.updateCpuStats();
@@ -6715,7 +6717,7 @@
/** Finish all activities in the stack without waiting. */
void finishAllActivitiesImmediately() {
if (!hasChild()) {
- removeIfPossible();
+ removeIfPossible("finishAllActivitiesImmediately");
return;
}
forAllActivities((r) -> {
@@ -8009,7 +8011,7 @@
// Task created by organizer are added as root.
final Task launchRootTask = mCreatedByOrganizer
- ? null : tda.updateLaunchRootTask(mWindowingMode);
+ ? null : tda.getLaunchRootTask(mWindowingMode, mActivityType);
if (launchRootTask != null) {
// Since this task will be put into a root task, its windowingMode will be
// inherited.
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 3f4150b..2ebdda6 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -28,7 +28,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.app.WindowConfiguration.isSplitScreenWindowingMode;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -54,6 +53,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.ToBooleanFunction;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.internal.util.function.pooled.PooledPredicate;
@@ -117,8 +117,18 @@
private RootWindowContainer mRootWindowContainer;
- // When non-null, new tasks get put into this root task.
- Task mLaunchRootTask = null;
+ // Launch root tasks by activityType then by windowingMode.
+ static private class LaunchRootTaskDef {
+ Task task;
+ int[] windowingModes;
+ int[] activityTypes;
+
+ boolean contains(int windowingMode, int activityType) {
+ return ArrayUtils.contains(windowingModes, windowingMode)
+ && ArrayUtils.contains(activityTypes, activityType);
+ }
+ }
+ private final ArrayList<LaunchRootTaskDef> mLaunchRootTasks = new ArrayList<>();
/**
* A focusable stack that is purposely to be positioned at the top. Although the stack may not
@@ -1017,7 +1027,7 @@
} else if (candidateTask != null) {
final Task stack = candidateTask;
final int position = onTop ? POSITION_TOP : POSITION_BOTTOM;
- Task launchRootTask = updateLaunchRootTask(windowingMode);
+ final Task launchRootTask = getLaunchRootTask(windowingMode, activityType);
if (launchRootTask != null) {
if (stack.getParent() == null) {
@@ -1096,40 +1106,41 @@
.build();
}
- /** @return the root task to create the next task in. */
- Task updateLaunchRootTask(int windowingMode) {
- if (!isSplitScreenWindowingMode(windowingMode)) {
- // Only split-screen windowing modes can do this currently...
- return null;
+ // TODO: Also clear when task is removed from system?
+ void setLaunchRootTask(Task rootTask, int[] windowingModes, int[] activityTypes) {
+ if (!rootTask.mCreatedByOrganizer) {
+ throw new IllegalArgumentException(
+ "Can't set not mCreatedByOrganizer as launch root tr=" + rootTask);
}
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowContainer child = mChildren.get(i);
- if (child.asTaskDisplayArea() != null) {
- final Task t = child.asTaskDisplayArea().updateLaunchRootTask(windowingMode);
- if (t != null) {
- return t;
- }
- continue;
- }
- final Task t = mChildren.get(i).asTask();
- if (t == null || !t.mCreatedByOrganizer
- || t.getRequestedOverrideWindowingMode() != windowingMode) {
- continue;
- }
- // If not already set, pick a launch root which is not the one we are launching into.
- if (mLaunchRootTask == null) {
- for (int j = 0, n = mChildren.size(); j < n; ++j) {
- final Task tt = mChildren.get(j).asTask();
- if (tt != null && tt.mCreatedByOrganizer && tt != t) {
- mLaunchRootTask = tt;
- break;
- }
- }
- }
- return t;
+ LaunchRootTaskDef def = null;
+ for (int i = mLaunchRootTasks.size() - 1; i >= 0; --i) {
+ if (mLaunchRootTasks.get(i).task.mTaskId != rootTask.mTaskId) continue;
+ def = mLaunchRootTasks.get(i);
}
- return mLaunchRootTask;
+
+ if (def != null) {
+ // Remove so we add to the end of the list.
+ mLaunchRootTasks.remove(def);
+ } else {
+ def = new LaunchRootTaskDef();
+ def.task = rootTask;
+ }
+
+ def.activityTypes = activityTypes;
+ def.windowingModes = windowingModes;
+ if (!ArrayUtils.isEmpty(windowingModes) || !ArrayUtils.isEmpty(activityTypes)) {
+ mLaunchRootTasks.add(def);
+ }
+ }
+
+ Task getLaunchRootTask(int windowingMode, int activityType) {
+ for (int i = mLaunchRootTasks.size() - 1; i >= 0; --i) {
+ if (mLaunchRootTasks.get(i).contains(windowingMode, activityType)) {
+ return mLaunchRootTasks.get(i).task;
+ }
+ }
+ return null;
}
/**
@@ -1321,7 +1332,6 @@
void onSplitScreenModeDismissed(Task toTop) {
mAtmService.deferWindowLayout();
try {
- mLaunchRootTask = null;
moveSplitScreenTasksToFullScreen();
} finally {
final Task topFullscreenStack = toTop != null
@@ -1919,7 +1929,20 @@
if (mLastFocusedRootTask != null) {
pw.println(doublePrefix + "mLastFocusedRootTask=" + mLastFocusedRootTask);
}
+
final String triplePrefix = doublePrefix + " ";
+
+ if (mLaunchRootTasks.size() > 0) {
+ pw.println(doublePrefix + "mLaunchRootTasks:");
+ for (int i = mLaunchRootTasks.size() - 1; i >= 0; --i) {
+ final LaunchRootTaskDef def = mLaunchRootTasks.get(i);
+ pw.println(triplePrefix
+ + def.activityTypes + " "
+ + def.windowingModes + " "
+ + " task=" + def.task);
+ }
+ }
+
pw.println(doublePrefix + "Application tokens in top down Z order:");
for (int index = getChildCount() - 1; index >= 0; --index) {
final WindowContainer child = getChildAt(index);
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 089071f..9a83ac7 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -510,7 +510,7 @@
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Delete root task display=%d winMode=%d",
task.getDisplayId(), task.getWindowingMode());
- task.removeImmediately();
+ task.removeImmediately("deleteRootTask");
return true;
}
} finally {
@@ -601,45 +601,6 @@
}
@Override
- public void setLaunchRoot(int displayId, @Nullable WindowContainerToken token) {
- enforceTaskPermission("setLaunchRoot()");
- final long origId = Binder.clearCallingIdentity();
- try {
- synchronized (mGlobalLock) {
- TaskDisplayArea defaultTaskDisplayArea = mService.mRootWindowContainer
- .getDisplayContent(displayId).getDefaultTaskDisplayArea();
- if (defaultTaskDisplayArea == null) {
- return;
- }
- WindowContainer wc = null;
- if (token != null) {
- wc = WindowContainer.fromBinder(token.asBinder());
- if (wc == null) {
- throw new IllegalArgumentException("Can't resolve window from token");
- }
- }
- final Task task = wc == null ? null : wc.asTask();
- if (task == null) {
- defaultTaskDisplayArea.mLaunchRootTask = null;
- return;
- }
- if (!task.mCreatedByOrganizer) {
- throw new IllegalArgumentException("Attempt to set task not created by "
- + "organizer as launch root task=" + task);
- }
- if (task.getDisplayArea() == null
- || task.getDisplayArea().getDisplayId() != displayId) {
- throw new RuntimeException("Can't set launch root for display " + displayId
- + " to task on display " + task.getDisplayContent().getDisplayId());
- }
- task.getDisplayArea().mLaunchRootTask = task;
- }
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- }
-
- @Override
public List<RunningTaskInfo> getChildTasks(WindowContainerToken parent,
@Nullable int[] activityTypes) {
enforceTaskPermission("getChildTasks()");
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 4eeae6c..a034bac9 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3238,6 +3238,11 @@
@Override
public void closeSystemDialogs(String reason) {
+ int callingPid = Binder.getCallingPid();
+ int callingUid = Binder.getCallingUid();
+ if (!mAtmInternal.checkCanCloseSystemDialogs(callingPid, callingUid, null)) {
+ return;
+ }
synchronized (mGlobalLock) {
mRoot.closeSystemDialogs(reason);
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index b0e67ce..be1f7e1 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -17,6 +17,10 @@
package com.android.server.wm;
import static android.Manifest.permission.READ_FRAME_BUFFER;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
import static com.android.server.wm.ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED;
@@ -49,13 +53,16 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.function.pooled.PooledConsumer;
import com.android.internal.util.function.pooled.PooledLambda;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.function.Consumer;
/**
* Server side implementation for the interface for organizing windows
@@ -256,34 +263,46 @@
final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();
for (int i = 0, n = hops.size(); i < n; ++i) {
final WindowContainerTransaction.HierarchyOp hop = hops.get(i);
- final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());
- if (wc == null || !wc.isAttached()) {
- Slog.e(TAG, "Attempt to operate on detached container: " + wc);
- continue;
- }
- if (syncId >= 0) {
- addToSyncSet(syncId, wc);
- }
- if (transition != null) {
- transition.collect(wc);
- if (hop.isReparent()) {
- if (wc.getParent() != null) {
- // Collect the current parent. It's visibility may change as a result
- // of this reparenting.
- transition.collect(wc.getParent());
+ switch (hop.getType()) {
+ case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT:
+ final Task task = WindowContainer.fromBinder(hop.getContainer()).asTask();
+ task.getDisplayArea().setLaunchRootTask(task,
+ hop.getWindowingModes(), hop.getActivityTypes());
+ break;
+ case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT:
+ effects |= reparentChildrenTasksHierarchyOp(hop, transition, syncId);
+ break;
+ case HIERARCHY_OP_TYPE_REORDER:
+ case HIERARCHY_OP_TYPE_REPARENT:
+ final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());
+ if (wc == null || !wc.isAttached()) {
+ Slog.e(TAG, "Attempt to operate on detached container: " + wc);
+ continue;
}
- if (hop.getNewParent() != null) {
- final WindowContainer parentWc =
- WindowContainer.fromBinder(hop.getNewParent());
- if (parentWc == null) {
- Slog.e(TAG, "Can't resolve parent window from token");
- continue;
+ if (syncId >= 0) {
+ addToSyncSet(syncId, wc);
+ }
+ if (transition != null) {
+ transition.collect(wc);
+ if (hop.isReparent()) {
+ if (wc.getParent() != null) {
+ // Collect the current parent. It's visibility may change as
+ // a result of this reparenting.
+ transition.collect(wc.getParent());
+ }
+ if (hop.getNewParent() != null) {
+ final WindowContainer parentWc =
+ WindowContainer.fromBinder(hop.getNewParent());
+ if (parentWc == null) {
+ Slog.e(TAG, "Can't resolve parent window from token");
+ continue;
+ }
+ transition.collect(parentWc);
+ }
}
- transition.collect(parentWc);
}
- }
+ effects |= sanitizeAndApplyHierarchyOp(wc, hop);
}
- effects |= sanitizeAndApplyHierarchyOp(wc, hop);
}
// Queue-up bounds-change transactions for tasks which are now organized. Do
// this after hierarchy ops so we have the final organized state.
@@ -492,6 +511,85 @@
return TRANSACT_EFFECTS_LIFECYCLE;
}
+ private int reparentChildrenTasksHierarchyOp(WindowContainerTransaction.HierarchyOp hop,
+ @Nullable Transition transition, int syncId) {
+ WindowContainer currentParent = hop.getContainer() != null
+ ? WindowContainer.fromBinder(hop.getContainer()) : null;
+ WindowContainer newParent = hop.getNewParent() != null
+ ? WindowContainer.fromBinder(hop.getNewParent()) : null;
+ if (currentParent == null && newParent == null) {
+ throw new IllegalArgumentException("reparentChildrenTasksHierarchyOp: " + hop);
+ } else if (currentParent == null) {
+ currentParent = newParent.asTask().getDisplayContent().getDefaultTaskDisplayArea();
+ } else if (newParent == null) {
+ newParent = currentParent.asTask().getDisplayContent().getDefaultTaskDisplayArea();
+ }
+
+ if (currentParent == newParent) {
+ Slog.e(TAG, "reparentChildrenTasksHierarchyOp parent not changing: " + hop);
+ return 0;
+ }
+ if (!currentParent.isAttached()) {
+ Slog.e(TAG, "reparentChildrenTasksHierarchyOp currentParent detached="
+ + currentParent + " hop=" + hop);
+ return 0;
+ }
+ if (!newParent.isAttached()) {
+ Slog.e(TAG, "reparentChildrenTasksHierarchyOp newParent detached="
+ + newParent + " hop=" + hop);
+ return 0;
+ }
+
+ final boolean newParentInMultiWindow = newParent.inMultiWindowMode();
+ final WindowContainer finalCurrentParent = currentParent;
+ Slog.i(TAG, "reparentChildrenTasksHierarchyOp"
+ + " currentParent=" + currentParent + " newParent=" + newParent + " hop=" + hop);
+
+ // We want to collect the tasks first before re-parenting to avoid array shifting on us.
+ final ArrayList<Task> tasksToReparent = new ArrayList<>();
+
+ currentParent.forAllTasks((Consumer<Task>) (task) -> {
+ Slog.i(TAG, " Processing task=" + task);
+ if (task.mCreatedByOrganizer
+ || task.getParent() != finalCurrentParent) {
+ // We only care about non-organized task that are direct children of the thing we
+ // are reparenting from.
+ return;
+ }
+
+ if (newParentInMultiWindow && !task.isResizeable()) {
+ Slog.e(TAG, "reparentChildrenTasksHierarchyOp non-resizeable task=" + task);
+ }
+
+ if (!ArrayUtils.contains(hop.getActivityTypes(), task.getActivityType())) return;
+ if (!ArrayUtils.contains(hop.getWindowingModes(), task.getWindowingMode())) return;
+
+ tasksToReparent.add(task);
+ }, !hop.getToTop());
+
+ final int count = tasksToReparent.size();
+ for (int i = 0; i < count; ++i) {
+ final Task task = tasksToReparent.get(i);
+ if (syncId >= 0) {
+ addToSyncSet(syncId, task);
+ }
+ if (transition != null) transition.collect(task);
+
+ if (newParent instanceof TaskDisplayArea) {
+ // For now, reparenting to display area is different from other reparents...
+ task.reparent((TaskDisplayArea) newParent, hop.getToTop());
+ } else {
+ task.reparent((Task) newParent,
+ hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM,
+ false /*moveParents*/, "processChildrenTaskReparentHierarchyOp");
+ }
+ }
+
+ if (transition != null) transition.collect(newParent);
+
+ return TRANSACT_EFFECTS_LIFECYCLE;
+ }
+
private void sanitizeWindowContainer(WindowContainer wc) {
if (!(wc instanceof Task) && !(wc instanceof DisplayArea)) {
throw new RuntimeException("Invalid token in task or displayArea transaction");
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 8aa154b..663d91e 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -23,6 +23,7 @@
import static android.view.Display.INVALID_DISPLAY;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
+import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.server.am.ActivityManagerService.MY_PID;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVITY_STARTS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RELEASE;
@@ -161,6 +162,8 @@
private volatile boolean mDebugging;
// Active instrumentation running in process?
private volatile boolean mInstrumenting;
+ // If there is active instrumentation, this is the source
+ private volatile int mInstrumentationSourceUid = -1;
// Active instrumentation with background activity starts privilege running in process?
private volatile boolean mInstrumentingWithBackgroundActivityStartPrivileges;
// This process it perceptible by the user.
@@ -623,9 +626,16 @@
mBoundClientUids = boundClientUids;
}
- public void setInstrumenting(boolean instrumenting,
+ /**
+ * Set instrumentation-related info.
+ *
+ * If {@code instrumenting} is {@code false}, {@code sourceUid} has to be -1.
+ */
+ public void setInstrumenting(boolean instrumenting, int sourceUid,
boolean hasBackgroundActivityStartPrivileges) {
+ checkArgument(instrumenting || sourceUid == -1);
mInstrumenting = instrumenting;
+ mInstrumentationSourceUid = sourceUid;
mInstrumentingWithBackgroundActivityStartPrivileges = hasBackgroundActivityStartPrivileges;
}
@@ -633,6 +643,11 @@
return mInstrumenting;
}
+ /** Returns the uid of the active instrumentation source if there is one, otherwise -1. */
+ int getInstrumentationSourceUid() {
+ return mInstrumentationSourceUid;
+ }
+
public void setPerceptible(boolean perceptible) {
mPerceptible = perceptible;
}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 996462f..13078b6 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -171,7 +171,6 @@
static_libs: [
"android.hardware.broadcastradio@common-utils-1x-lib",
- "libservice-connectivity-static",
],
product_variables: {
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 85ef394..1893321 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -41,8 +41,6 @@
int register_android_server_vibrator_VibratorController(JavaVM* vm, JNIEnv* env);
int register_android_server_VibratorManagerService(JNIEnv* env);
int register_android_server_location_GnssLocationProvider(JNIEnv* env);
-int register_android_server_connectivity_Vpn(JNIEnv* env);
-int register_android_server_TestNetworkService(JNIEnv* env);
int register_android_server_devicepolicy_CryptoTestHelper(JNIEnv*);
int register_android_server_tv_TvUinputBridge(JNIEnv* env);
int register_android_server_tv_TvInputHal(JNIEnv* env);
@@ -96,8 +94,6 @@
register_android_server_VibratorManagerService(env);
register_android_server_SystemServer(env);
register_android_server_location_GnssLocationProvider(env);
- register_android_server_connectivity_Vpn(env);
- register_android_server_TestNetworkService(env);
register_android_server_devicepolicy_CryptoTestHelper(env);
register_android_server_ConsumerIrService(env);
register_android_server_BatteryStatsService(env);
diff --git a/services/core/xsd/platform-compat-config.xsd b/services/core/xsd/platform-compat-config.xsd
index 9924708..a62e2c3 100644
--- a/services/core/xsd/platform-compat-config.xsd
+++ b/services/core/xsd/platform-compat-config.xsd
@@ -31,6 +31,7 @@
<xs:attribute type="xs:int" name="enableAfterTargetSdk"/>
<xs:attribute type="xs:int" name="enableSinceTargetSdk"/>
<xs:attribute type="xs:string" name="description"/>
+ <xs:attribute type="xs:boolean" name="overridable"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
@@ -48,7 +49,3 @@
</xs:unique>
</xs:element>
</xs:schema>
-
-
-
-
diff --git a/services/core/xsd/platform-compat-schema/current.txt b/services/core/xsd/platform-compat-schema/current.txt
index e3640ed..fb8bbef 100644
--- a/services/core/xsd/platform-compat-schema/current.txt
+++ b/services/core/xsd/platform-compat-schema/current.txt
@@ -10,6 +10,7 @@
method public long getId();
method public boolean getLoggingOnly();
method public String getName();
+ method public boolean getOverridable();
method public String getValue();
method public void setDescription(String);
method public void setDisabled(boolean);
@@ -18,6 +19,7 @@
method public void setId(long);
method public void setLoggingOnly(boolean);
method public void setName(String);
+ method public void setOverridable(boolean);
method public void setValue(String);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index a281180..48f8b15 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -134,6 +134,8 @@
private static final String TAG_ALWAYS_ON_VPN_LOCKDOWN = "vpn-lockdown";
private static final String TAG_COMMON_CRITERIA_MODE = "common-criteria-mode";
private static final String TAG_PASSWORD_COMPLEXITY = "password-complexity";
+ private static final String TAG_ORGANIZATION_ID = "organization-id";
+ private static final String TAG_ENROLLMENT_SPECIFIC_ID = "enrollment-specific-id";
private static final String ATTR_VALUE = "value";
private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification";
private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications";
@@ -273,6 +275,8 @@
public String mAlwaysOnVpnPackage;
public boolean mAlwaysOnVpnLockdown;
boolean mCommonCriteriaMode;
+ public String mOrganizationId;
+ public String mEnrollmentSpecificId;
ActiveAdmin(DeviceAdminInfo info, boolean isParent) {
this.info = info;
@@ -533,6 +537,12 @@
if (mPasswordComplexity != PASSWORD_COMPLEXITY_NONE) {
writeAttributeValueToXml(out, TAG_PASSWORD_COMPLEXITY, mPasswordComplexity);
}
+ if (!TextUtils.isEmpty(mOrganizationId)) {
+ writeTextToXml(out, TAG_ORGANIZATION_ID, mOrganizationId);
+ }
+ if (!TextUtils.isEmpty(mEnrollmentSpecificId)) {
+ writeTextToXml(out, TAG_ENROLLMENT_SPECIFIC_ID, mEnrollmentSpecificId);
+ }
}
void writeTextToXml(TypedXmlSerializer out, String tag, String text) throws IOException {
@@ -766,6 +776,22 @@
mCommonCriteriaMode = parser.getAttributeBoolean(null, ATTR_VALUE, false);
} else if (TAG_PASSWORD_COMPLEXITY.equals(tag)) {
mPasswordComplexity = parser.getAttributeInt(null, ATTR_VALUE);
+ } else if (TAG_ORGANIZATION_ID.equals(tag)) {
+ type = parser.next();
+ if (type == TypedXmlPullParser.TEXT) {
+ mOrganizationId = parser.getText();
+ } else {
+ Log.w(DevicePolicyManagerService.LOG_TAG,
+ "Missing Organization ID.");
+ }
+ } else if (TAG_ENROLLMENT_SPECIFIC_ID.equals(tag)) {
+ type = parser.next();
+ if (type == TypedXmlPullParser.TEXT) {
+ mEnrollmentSpecificId = parser.getText();
+ } else {
+ Log.w(DevicePolicyManagerService.LOG_TAG,
+ "Missing Enrollment-specific ID.");
+ }
} else {
Slog.w(DevicePolicyManagerService.LOG_TAG, "Unknown admin tag: " + tag);
XmlUtils.skipCurrentTag(parser);
@@ -1107,5 +1133,15 @@
pw.print("mPasswordComplexity=");
pw.println(mPasswordComplexity);
+
+ if (!TextUtils.isEmpty(mOrganizationId)) {
+ pw.print("mOrganizationId=");
+ pw.println(mOrganizationId);
+ }
+
+ if (!TextUtils.isEmpty(mEnrollmentSpecificId)) {
+ pw.print("mEnrollmentSpecificId=");
+ pw.println(mEnrollmentSpecificId);
+ }
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index 6f1d451e..8b7f83f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -15,6 +15,7 @@
*/
package com.android.server.devicepolicy;
+import android.annotation.NonNull;
import android.app.admin.DevicePolicySafetyChecker;
import android.app.admin.IDevicePolicyManager;
import android.content.ComponentName;
@@ -54,6 +55,12 @@
*/
abstract void handleUnlockUser(int userId);
/**
+ * To be called by {@link DevicePolicyManagerService#Lifecycle} after a user is being unlocked.
+ *
+ * @see {@link SystemService#onUserUnlocked}
+ */
+ abstract void handleOnUserUnlocked(int userId);
+ /**
* To be called by {@link DevicePolicyManagerService#Lifecycle} when a user is being stopped.
*
* @see {@link SystemService#onUserStopping}
@@ -101,4 +108,11 @@
public boolean canProfileOwnerResetPasswordWhenLocked(int userId) {
return false;
}
+
+ public String getEnrollmentSpecificId() {
+ return "";
+ }
+
+ public void setOrganizationIdForUser(
+ @NonNull String callerPackage, @NonNull String enterpriseId, int userId) {}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
index 7ec5ff0..5e7f984 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
@@ -78,6 +78,12 @@
private static final String ATTR_DEVICE_PROVISIONING_CONFIG_APPLIED =
"device-provisioning-config-applied";
private static final String ATTR_DEVICE_PAIRED = "device-paired";
+ private static final String ATTR_NEW_USER_DISCLAIMER = "new-user-disclaimer";
+
+ // Values of ATTR_NEW_USER_DISCLAIMER
+ static final String NEW_USER_DISCLAIMER_SHOWN = "shown";
+ static final String NEW_USER_DISCLAIMER_NOT_NEEDED = "not_needed";
+ static final String NEW_USER_DISCLAIMER_NEEDED = "needed";
private static final String TAG = DevicePolicyManagerService.LOG_TAG;
private static final boolean VERBOSE_LOG = false; // DO NOT SUBMIT WITH TRUE
@@ -146,6 +152,10 @@
// apps were suspended or unsuspended.
boolean mAppsSuspended = false;
+ // Whether it's necessary to show a disclaimer (that the device is managed) after the user
+ // starts.
+ String mNewUserDisclaimer = NEW_USER_DISCLAIMER_NOT_NEEDED;
+
DevicePolicyData(@UserIdInt int userId) {
mUserId = userId;
}
@@ -186,6 +196,9 @@
if (policyData.mPermissionPolicy != DevicePolicyManager.PERMISSION_POLICY_PROMPT) {
out.attributeInt(null, ATTR_PERMISSION_POLICY, policyData.mPermissionPolicy);
}
+ if (NEW_USER_DISCLAIMER_NEEDED.equals(policyData.mNewUserDisclaimer)) {
+ out.attribute(null, ATTR_NEW_USER_DISCLAIMER, policyData.mNewUserDisclaimer);
+ }
// Serialize delegations.
for (int i = 0; i < policyData.mDelegationMap.size(); ++i) {
@@ -412,6 +425,7 @@
if (permissionPolicy != -1) {
policy.mPermissionPolicy = permissionPolicy;
}
+ policy.mNewUserDisclaimer = parser.getAttributeValue(null, ATTR_NEW_USER_DISCLAIMER);
int outerDepth = parser.getDepth();
policy.mLockTaskPackages.clear();
@@ -588,6 +602,7 @@
pw.print("mAppsSuspended="); pw.println(mAppsSuspended);
pw.print("mUserSetupComplete="); pw.println(mUserSetupComplete);
pw.print("mAffiliationIds="); pw.println(mAffiliationIds);
+ pw.print("mNewUserDisclaimer="); pw.println(mNewUserDisclaimer);
pw.decreaseIndent();
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 3e51b75..8b575fb 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -707,6 +707,11 @@
public void onUserStopping(@NonNull TargetUser user) {
mService.handleStopUser(user.getUserIdentifier());
}
+
+ @Override
+ public void onUserUnlocked(@NonNull TargetUser user) {
+ mService.handleOnUserUnlocked(user.getUserIdentifier());
+ }
}
@GuardedBy("getLockObject()")
@@ -819,6 +824,7 @@
} else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)
&& !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
handlePackagesChanged(intent.getData().getSchemeSpecificPart(), userHandle);
+ removeCredentialManagementApp(intent.getData().getSchemeSpecificPart());
} else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action)) {
clearWipeProfileNotification();
} else if (Intent.ACTION_DATE_CHANGED.equals(action)
@@ -885,6 +891,14 @@
}
}
+ private final class UserLifecycleListener implements UserManagerInternal.UserLifecycleListener {
+
+ @Override
+ public void onUserCreated(UserInfo user) {
+ mHandler.post(() -> handleNewUserCreated(user));
+ }
+ }
+
private void handlePackagesChanged(@Nullable String packageName, int userHandle) {
boolean removedAdmin = false;
if (VERBOSE_LOG) {
@@ -949,6 +963,20 @@
}
}
+ private void removeCredentialManagementApp(String packageName) {
+ mBackgroundHandler.post(() -> {
+ try (KeyChainConnection connection = mInjector.keyChainBind()) {
+ IKeyChainService service = connection.getService();
+ if (service.hasCredentialManagementApp()
+ && packageName.equals(service.getCredentialManagementAppPackageName())) {
+ service.removeCredentialManagementApp();
+ }
+ } catch (RemoteException | InterruptedException | IllegalStateException e) {
+ Log.e(LOG_TAG, "Unable to remove the credential management app");
+ }
+ });
+ }
+
private boolean isRemovedPackage(String changedPackage, String targetPackage, int userHandle) {
try {
return targetPackage != null
@@ -1419,6 +1447,10 @@
return SecurityLog.isLoggingEnabled();
}
+ KeyChainConnection keyChainBind() throws InterruptedException {
+ return KeyChain.bind(mContext);
+ }
+
KeyChainConnection keyChainBindAsUser(UserHandle user) throws InterruptedException {
return KeyChain.bindAsUser(mContext, user);
}
@@ -1542,6 +1574,7 @@
mSetupContentObserver = new SetupContentObserver(mHandler);
mUserManagerInternal.addUserRestrictionsListener(new RestrictionsListener(mContext));
+ mUserManagerInternal.addUserLifecycleListener(new UserLifecycleListener());
loadOwners();
}
@@ -2883,6 +2916,11 @@
}
@Override
+ void handleOnUserUnlocked(int userId) {
+ showNewUserDisclaimerIfNecessary(userId);
+ }
+
+ @Override
void handleStopUser(int userId) {
stopOwnerService(userId, "stop-user");
}
@@ -3413,6 +3451,19 @@
synchronized (getLockObject()) {
ActiveAdmin ap = getActiveAdminForCallerLocked(
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
+
+ // If setPasswordQuality is called on the parent, ensure that
+ // the primary admin does not have password complexity state (this is an
+ // unsupported state).
+ if (parent) {
+ final ActiveAdmin primaryAdmin = getActiveAdminForCallerLocked(
+ who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, false);
+ final boolean hasComplexitySet =
+ primaryAdmin.mPasswordComplexity != PASSWORD_COMPLEXITY_NONE;
+ Preconditions.checkState(!hasComplexitySet,
+ "Cannot set password quality when complexity is set on the primary admin."
+ + " Set the primary admin's complexity to NONE first.");
+ }
mInjector.binderWithCleanCallingIdentity(() -> {
final PasswordPolicy passwordPolicy = ap.mPasswordPolicy;
if (passwordPolicy.quality != quality) {
@@ -4378,6 +4429,20 @@
final ActiveAdmin admin = getParentOfAdminIfRequired(
getProfileOwnerOrDeviceOwnerLocked(caller), calledOnParent);
if (admin.mPasswordComplexity != passwordComplexity) {
+ // We require the caller to explicitly clear any password quality requirements set
+ // on the parent DPM instance, to avoid the case where password requirements are
+ // specified in the form of quality on the parent but complexity on the profile
+ // itself.
+ if (!calledOnParent) {
+ final boolean hasQualityRequirementsOnParent = admin.hasParentActiveAdmin()
+ && admin.getParentActiveAdmin().mPasswordPolicy.quality
+ != PASSWORD_QUALITY_UNSPECIFIED;
+ Preconditions.checkState(!hasQualityRequirementsOnParent,
+ "Password quality is set on the parent when attempting to set password"
+ + "complexity. Clear the quality by setting the password quality "
+ + "on the parent to PASSWORD_QUALITY_UNSPECIFIED first");
+ }
+
mInjector.binderWithCleanCallingIdentity(() -> {
admin.mPasswordComplexity = passwordComplexity;
// Reset the password policy.
@@ -7641,8 +7706,8 @@
// Sets profile owner on current foreground user since
// the human user will complete the DO setup workflow from there.
manageUserUnchecked(/* deviceOwner= */ admin, /* profileOwner= */ admin,
- /* managedUser= */ currentForegroundUser,
- /* adminExtras= */ null);
+ /* managedUser= */ currentForegroundUser, /* adminExtras= */ null,
+ /* showDisclaimer= */ false);
}
return true;
}
@@ -9694,7 +9759,8 @@
final long id = mInjector.binderClearCallingIdentity();
try {
- manageUserUnchecked(admin, profileOwner, userHandle, adminExtras);
+ manageUserUnchecked(admin, profileOwner, userHandle, adminExtras,
+ /* showDisclaimer= */ true);
if ((flags & DevicePolicyManager.SKIP_SETUP_WIZARD) != 0) {
Settings.Secure.putIntForUser(mContext.getContentResolver(),
@@ -9716,7 +9782,7 @@
}
private void manageUserUnchecked(ComponentName admin, ComponentName profileOwner,
- @UserIdInt int userId, PersistableBundle adminExtras) {
+ @UserIdInt int userId, PersistableBundle adminExtras, boolean showDisclaimer) {
final String adminPkg = admin.getPackageName();
try {
// Install the profile owner if not present.
@@ -9742,11 +9808,60 @@
DevicePolicyData policyData = getUserData(userId);
policyData.mInitBundle = adminExtras;
policyData.mAdminBroadcastPending = true;
-
+ policyData.mNewUserDisclaimer = showDisclaimer
+ ? DevicePolicyData.NEW_USER_DISCLAIMER_NEEDED
+ : DevicePolicyData.NEW_USER_DISCLAIMER_NOT_NEEDED;
saveSettingsLocked(userId);
}
}
+ private void handleNewUserCreated(UserInfo user) {
+ if (!mOwners.hasDeviceOwner()) return;
+
+ final int userId = user.id;
+ Log.i(LOG_TAG, "User " + userId + " added on DO mode; setting ShowNewUserDisclaimer");
+
+ setShowNewUserDisclaimer(userId, DevicePolicyData.NEW_USER_DISCLAIMER_NEEDED);
+ }
+
+ @Override
+ public void resetNewUserDisclaimer() {
+ CallerIdentity callerIdentity = getCallerIdentity();
+ canManageUsers(callerIdentity);
+
+ setShowNewUserDisclaimer(callerIdentity.getUserId(),
+ DevicePolicyData.NEW_USER_DISCLAIMER_SHOWN);
+ }
+
+ private void setShowNewUserDisclaimer(@UserIdInt int userId, String value) {
+ Slog.i(LOG_TAG, "Setting new user disclaimer for user " + userId + " as " + value);
+ synchronized (getLockObject()) {
+ DevicePolicyData policyData = getUserData(userId);
+ policyData.mNewUserDisclaimer = value;
+ saveSettingsLocked(userId);
+ }
+ }
+
+ private void showNewUserDisclaimerIfNecessary(@UserIdInt int userId) {
+ boolean mustShow;
+ synchronized (getLockObject()) {
+ DevicePolicyData policyData = getUserData(userId);
+ if (VERBOSE_LOG) {
+ Slog.v(LOG_TAG, "showNewUserDisclaimerIfNecessary(" + userId + "): "
+ + policyData.mNewUserDisclaimer + ")");
+ }
+ mustShow = DevicePolicyData.NEW_USER_DISCLAIMER_NEEDED
+ .equals(policyData.mNewUserDisclaimer);
+ }
+ if (!mustShow) return;
+
+ Intent intent = new Intent(DevicePolicyManager.ACTION_SHOW_NEW_USER_DISCLAIMER);
+
+ // TODO(b/172691310): add CTS tests to make sure disclaimer is shown
+ Slog.i(LOG_TAG, "Dispatching ACTION_SHOW_NEW_USER_DISCLAIMER intent");
+ mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
+ }
+
@Override
public boolean removeUser(ComponentName who, UserHandle userHandle) {
Objects.requireNonNull(who, "ComponentName is null");
@@ -15581,4 +15696,69 @@
return true;
}
}
+
+ @Override
+ public String getEnrollmentSpecificId() {
+ if (!mHasFeature) {
+ return "";
+ }
+
+ final CallerIdentity caller = getCallerIdentity();
+ Preconditions.checkCallAuthorization(
+ isDeviceOwner(caller) || isProfileOwner(caller));
+
+ synchronized (getLockObject()) {
+ final ActiveAdmin requiredAdmin = getDeviceOrProfileOwnerAdminLocked(
+ caller.getUserId());
+ final String esid = requiredAdmin.mEnrollmentSpecificId;
+ return esid != null ? esid : "";
+ }
+ }
+
+ @Override
+ public void setOrganizationIdForUser(
+ @NonNull String callerPackage, @NonNull String organizationId, int userId) {
+ if (!mHasFeature) {
+ return;
+ }
+ Objects.requireNonNull(callerPackage);
+
+ final CallerIdentity caller = getCallerIdentity(callerPackage);
+ // Only the DPC can set this ID.
+ Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller),
+ "Only a Device Owner or Profile Owner may set the Enterprise ID.");
+ // Empty enterprise ID must not be provided in calls to this method.
+ Preconditions.checkArgument(!TextUtils.isEmpty(organizationId),
+ "Enterprise ID may not be empty.");
+
+ Log.i(LOG_TAG,
+ String.format("Setting Enterprise ID to %s for user %d", organizationId, userId));
+
+ synchronized (getLockObject()) {
+ ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userId);
+ // As the caller is the system, it must specify the component name of the profile owner
+ // as a safety check.
+ Preconditions.checkCallAuthorization(
+ owner != null && owner.getUserHandle().getIdentifier() == userId,
+ String.format("The Profile Owner or Device Owner may only set the Enterprise ID"
+ + " on its own user, called on user %d but owner user is %d", userId,
+ owner.getUserHandle().getIdentifier()));
+ Preconditions.checkState(
+ TextUtils.isEmpty(owner.mOrganizationId) || owner.mOrganizationId.equals(
+ organizationId),
+ "The organization ID has been previously set to a different value and cannot "
+ + "be changed");
+ final String dpcPackage = owner.info.getPackageName();
+ mInjector.binderWithCleanCallingIdentity(() -> {
+ EnterpriseSpecificIdCalculator esidCalculator =
+ new EnterpriseSpecificIdCalculator(mContext);
+
+ final String esid = esidCalculator.calculateEnterpriseId(dpcPackage,
+ organizationId);
+ owner.mOrganizationId = organizationId;
+ owner.mEnrollmentSpecificId = esid;
+ saveSettingsLocked(userId);
+ });
+ }
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
new file mode 100644
index 0000000..df7f308
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
@@ -0,0 +1,145 @@
+/*
+ * 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.devicepolicy;
+
+import android.content.Context;
+import android.content.pm.VerifierDeviceIdentity;
+import android.net.wifi.WifiManager;
+import android.os.Build;
+import android.security.identity.Util;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.nio.ByteBuffer;
+
+class EnterpriseSpecificIdCalculator {
+ private static final int PADDED_HW_ID_LENGTH = 16;
+ private static final int PADDED_PROFILE_OWNER_LENGTH = 64;
+ private static final int PADDED_ENTERPRISE_ID_LENGTH = 64;
+ private static final int ESID_LENGTH = 16;
+
+ private final String mImei;
+ private final String mMeid;
+ private final String mSerialNumber;
+ private final String mMacAddress;
+
+ @VisibleForTesting
+ EnterpriseSpecificIdCalculator(String imei, String meid, String serialNumber,
+ String macAddress) {
+ mImei = imei;
+ mMeid = meid;
+ mSerialNumber = serialNumber;
+ mMacAddress = macAddress;
+ }
+
+ EnterpriseSpecificIdCalculator(Context context) {
+ TelephonyManager telephonyService = context.getSystemService(TelephonyManager.class);
+ Preconditions.checkState(telephonyService != null, "Unable to access telephony service");
+ mImei = telephonyService.getImei(0);
+ mMeid = telephonyService.getMeid(0);
+ mSerialNumber = Build.getSerial();
+ WifiManager wifiManager = context.getSystemService(WifiManager.class);
+ Preconditions.checkState(wifiManager != null, "Unable to access WiFi service");
+ final String[] macAddresses = wifiManager.getFactoryMacAddresses();
+ if (macAddresses == null || macAddresses.length == 0) {
+ mMacAddress = "";
+ } else {
+ mMacAddress = macAddresses[0];
+ }
+ }
+
+ private static String getPaddedTruncatedString(String input, int maxLength) {
+ final String paddedValue = String.format("%" + maxLength + "s", input);
+ return paddedValue.substring(0, maxLength);
+ }
+
+ private static String getPaddedHardwareIdentifier(String hardwareIdentifier) {
+ if (hardwareIdentifier == null) {
+ hardwareIdentifier = "";
+ }
+ return getPaddedTruncatedString(hardwareIdentifier, PADDED_HW_ID_LENGTH);
+ }
+
+ String getPaddedImei() {
+ return getPaddedHardwareIdentifier(mImei);
+ }
+
+ String getPaddedMeid() {
+ return getPaddedHardwareIdentifier(mMeid);
+ }
+
+ String getPaddedSerialNumber() {
+ return getPaddedHardwareIdentifier(mSerialNumber);
+ }
+
+ String getPaddedProfileOwnerName(String profileOwnerPackage) {
+ return getPaddedTruncatedString(profileOwnerPackage, PADDED_PROFILE_OWNER_LENGTH);
+ }
+
+ String getPaddedEnterpriseId(String enterpriseId) {
+ return getPaddedTruncatedString(enterpriseId, PADDED_ENTERPRISE_ID_LENGTH);
+ }
+
+ /**
+ * Calculates the ESID.
+ * @param profileOwnerPackage Package of the Device Policy Client that manages the device/
+ * profile. May not be null.
+ * @param enterpriseIdString The identifier for the enterprise in which the device/profile is
+ * being enrolled. This parameter may not be empty, but may be null.
+ * If called with {@code null}, will calculate an ESID with empty
+ * Enterprise ID.
+ */
+ public String calculateEnterpriseId(String profileOwnerPackage, String enterpriseIdString) {
+ Preconditions.checkArgument(!TextUtils.isEmpty(profileOwnerPackage),
+ "owner package must be specified.");
+
+ Preconditions.checkArgument(enterpriseIdString == null || !enterpriseIdString.isEmpty(),
+ "enterprise ID must either be null or non-empty.");
+
+ if (enterpriseIdString == null) {
+ enterpriseIdString = "";
+ }
+
+ final byte[] serialNumber = getPaddedSerialNumber().getBytes();
+ final byte[] imei = getPaddedImei().getBytes();
+ final byte[] meid = getPaddedMeid().getBytes();
+ final byte[] macAddress = mMacAddress.getBytes();
+ final int totalIdentifiersLength = serialNumber.length + imei.length + meid.length
+ + macAddress.length;
+ final ByteBuffer fixedIdentifiers = ByteBuffer.allocate(totalIdentifiersLength);
+ fixedIdentifiers.put(serialNumber);
+ fixedIdentifiers.put(imei);
+ fixedIdentifiers.put(meid);
+ fixedIdentifiers.put(macAddress);
+
+ final byte[] dpcPackage = getPaddedProfileOwnerName(profileOwnerPackage).getBytes();
+ final byte[] enterpriseId = getPaddedEnterpriseId(enterpriseIdString).getBytes();
+ final ByteBuffer info = ByteBuffer.allocate(dpcPackage.length + enterpriseId.length);
+ info.put(dpcPackage);
+ info.put(enterpriseId);
+ final byte[] esidBytes = Util.computeHkdf("HMACSHA256", fixedIdentifiers.array(), null,
+ info.array(), ESID_LENGTH);
+ ByteBuffer esidByteBuffer = ByteBuffer.wrap(esidBytes);
+
+ VerifierDeviceIdentity firstId = new VerifierDeviceIdentity(esidByteBuffer.getLong());
+ VerifierDeviceIdentity secondId = new VerifierDeviceIdentity(esidByteBuffer.getLong());
+ return firstId.toString() + secondId.toString();
+ }
+}
diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp
index a31aac9..d224428 100644
--- a/services/incremental/BinderIncrementalService.cpp
+++ b/services/incremental/BinderIncrementalService.cpp
@@ -122,13 +122,14 @@
const ::android::sp<::android::content::pm::IDataLoaderStatusListener>& statusListener,
const ::android::os::incremental::StorageHealthCheckParams& healthCheckParams,
const ::android::sp<::android::os::incremental::IStorageHealthListener>& healthListener,
+ const ::std::vector<::android::os::incremental::PerUidReadTimeouts>& perUidReadTimeouts,
int32_t* _aidl_return) {
*_aidl_return =
mImpl.createStorage(path, const_cast<content::pm::DataLoaderParamsParcel&&>(params),
android::incremental::IncrementalService::CreateOptions(createMode),
statusListener,
const_cast<StorageHealthCheckParams&&>(healthCheckParams),
- healthListener);
+ healthListener, perUidReadTimeouts);
return ok();
}
@@ -164,8 +165,8 @@
return ok();
}
-binder::Status BinderIncrementalService::disableReadLogs(int32_t storageId) {
- mImpl.disableReadLogs(storageId);
+binder::Status BinderIncrementalService::disallowReadLogs(int32_t storageId) {
+ mImpl.disallowReadLogs(storageId);
return ok();
}
@@ -254,7 +255,7 @@
binder::Status BinderIncrementalService::getLoadingProgress(int32_t storageId,
float* _aidl_return) {
- *_aidl_return = mImpl.getLoadingProgress(storageId);
+ *_aidl_return = mImpl.getLoadingProgress(storageId).getProgress();
return ok();
}
diff --git a/services/incremental/BinderIncrementalService.h b/services/incremental/BinderIncrementalService.h
index 8afa0f7..9a4537a 100644
--- a/services/incremental/BinderIncrementalService.h
+++ b/services/incremental/BinderIncrementalService.h
@@ -45,6 +45,7 @@
const ::android::sp<::android::content::pm::IDataLoaderStatusListener>& statusListener,
const ::android::os::incremental::StorageHealthCheckParams& healthCheckParams,
const ::android::sp<IStorageHealthListener>& healthListener,
+ const ::std::vector<::android::os::incremental::PerUidReadTimeouts>& perUidReadTimeouts,
int32_t* _aidl_return) final;
binder::Status createLinkedStorage(const std::string& path, int32_t otherStorageId,
int32_t createMode, int32_t* _aidl_return) final;
@@ -77,7 +78,7 @@
std::vector<uint8_t>* _aidl_return) final;
binder::Status startLoading(int32_t storageId, bool* _aidl_return) final;
binder::Status deleteStorage(int32_t storageId) final;
- binder::Status disableReadLogs(int32_t storageId) final;
+ binder::Status disallowReadLogs(int32_t storageId) final;
binder::Status configureNativeBinaries(int32_t storageId, const std::string& apkFullPath,
const std::string& libDirRelativePath,
const std::string& abi, bool extractNativeLibs,
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index eb6b325..dde70ca 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -38,9 +38,11 @@
using namespace std::literals;
-constexpr const char* kDataUsageStats = "android.permission.LOADER_USAGE_STATS";
+constexpr const char* kLoaderUsageStats = "android.permission.LOADER_USAGE_STATS";
constexpr const char* kOpUsage = "android:loader_usage_stats";
+constexpr const char* kInteractAcrossUsers = "android.permission.INTERACT_ACROSS_USERS";
+
namespace android::incremental {
using content::pm::DataLoaderParamsParcel;
@@ -63,6 +65,10 @@
static constexpr auto libSuffix = ".so"sv;
static constexpr auto blockSize = 4096;
static constexpr auto systemPackage = "android"sv;
+
+ static constexpr auto progressUpdateInterval = 1000ms;
+ static constexpr auto perUidTimeoutOffset = progressUpdateInterval * 2;
+ static constexpr auto minPerUidTimeout = progressUpdateInterval * 3;
};
static const Constants& constants() {
@@ -350,7 +356,8 @@
dprintf(fd, " storages (%d): {\n", int(mnt.storages.size()));
for (auto&& [storageId, storage] : mnt.storages) {
dprintf(fd, " [%d] -> [%s] (%d %% loaded) \n", storageId, storage.name.c_str(),
- (int)(getLoadingProgressFromPath(mnt, storage.name.c_str()) * 100));
+ (int)(getLoadingProgressFromPath(mnt, storage.name.c_str()).getProgress() *
+ 100));
}
dprintf(fd, " }\n");
@@ -419,12 +426,11 @@
}
}
-StorageId IncrementalService::createStorage(std::string_view mountPoint,
- content::pm::DataLoaderParamsParcel&& dataLoaderParams,
- CreateOptions options,
- const DataLoaderStatusListener& statusListener,
- StorageHealthCheckParams&& healthCheckParams,
- const StorageHealthListener& healthListener) {
+StorageId IncrementalService::createStorage(
+ std::string_view mountPoint, content::pm::DataLoaderParamsParcel&& dataLoaderParams,
+ CreateOptions options, const DataLoaderStatusListener& statusListener,
+ StorageHealthCheckParams&& healthCheckParams, const StorageHealthListener& healthListener,
+ const std::vector<PerUidReadTimeouts>& perUidReadTimeouts) {
LOG(INFO) << "createStorage: " << mountPoint << " | " << int(options);
if (!path::isAbsolute(mountPoint)) {
LOG(ERROR) << "path is not absolute: " << mountPoint;
@@ -553,13 +559,14 @@
if (auto err = addBindMount(*ifs, storageIt->first, storageIt->second.name,
std::string(storageIt->second.name), std::move(mountNorm), bk, l);
err < 0) {
- LOG(ERROR) << "adding bind mount failed: " << -err;
+ LOG(ERROR) << "Adding bind mount failed: " << -err;
return kInvalidStorageId;
}
// Done here as well, all data structures are in good state.
secondCleanupOnFailure.release();
+ // DataLoader.
auto dataLoaderStub = prepareDataLoader(*ifs, std::move(dataLoaderParams), &statusListener,
std::move(healthCheckParams), &healthListener);
CHECK(dataLoaderStub);
@@ -567,6 +574,11 @@
mountIt->second = std::move(ifs);
l.unlock();
+ // Per Uid timeouts.
+ if (!perUidReadTimeouts.empty()) {
+ setUidReadTimeouts(mountId, perUidReadTimeouts);
+ }
+
if (mSystemReady.load(std::memory_order_relaxed) && !dataLoaderStub->requestCreate()) {
// failed to create data loader
LOG(ERROR) << "initializeDataLoader() failed";
@@ -634,17 +646,17 @@
return it->second->second.storage;
}
-void IncrementalService::disableReadLogs(StorageId storageId) {
+void IncrementalService::disallowReadLogs(StorageId storageId) {
std::unique_lock l(mLock);
const auto ifs = getIfsLocked(storageId);
if (!ifs) {
- LOG(ERROR) << "disableReadLogs failed, invalid storageId: " << storageId;
+ LOG(ERROR) << "disallowReadLogs failed, invalid storageId: " << storageId;
return;
}
- if (!ifs->readLogsEnabled()) {
+ if (!ifs->readLogsAllowed()) {
return;
}
- ifs->disableReadLogs();
+ ifs->disallowReadLogs();
l.unlock();
const auto metadata = constants().readLogsDisabledMarkerName;
@@ -669,15 +681,26 @@
const auto& params = ifs->dataLoaderStub->params();
if (enableReadLogs) {
- if (!ifs->readLogsEnabled()) {
+ if (!ifs->readLogsAllowed()) {
LOG(ERROR) << "setStorageParams failed, readlogs disabled for storageId: " << storageId;
return -EPERM;
}
- if (auto status = mAppOpsManager->checkPermission(kDataUsageStats, kOpUsage,
+ // Check loader usage stats permission and apop.
+ if (auto status = mAppOpsManager->checkPermission(kLoaderUsageStats, kOpUsage,
params.packageName.c_str());
!status.isOk()) {
- LOG(ERROR) << "checkPermission failed: " << status.toString8();
+ LOG(ERROR) << " Permission: " << kLoaderUsageStats
+ << " check failed: " << status.toString8();
+ return fromBinderStatus(status);
+ }
+
+ // Check multiuser permission.
+ if (auto status = mAppOpsManager->checkPermission(kInteractAcrossUsers, nullptr,
+ params.packageName.c_str());
+ !status.isOk()) {
+ LOG(ERROR) << " Permission: " << kInteractAcrossUsers
+ << " check failed: " << status.toString8();
return fromBinderStatus(status);
}
}
@@ -704,7 +727,12 @@
}
std::lock_guard l(mMountOperationLock);
- return mVold->setIncFsMountOptions(control, enableReadLogs);
+ const auto status = mVold->setIncFsMountOptions(control, enableReadLogs);
+ if (status.isOk()) {
+ // Store enabled state.
+ ifs.setReadLogsEnabled(enableReadLogs);
+ }
+ return status;
}
void IncrementalService::deleteStorage(StorageId storageId) {
@@ -1052,6 +1080,74 @@
return true;
}
+void IncrementalService::setUidReadTimeouts(
+ StorageId storage, const std::vector<PerUidReadTimeouts>& perUidReadTimeouts) {
+ using microseconds = std::chrono::microseconds;
+ using milliseconds = std::chrono::milliseconds;
+
+ auto maxPendingTimeUs = microseconds(0);
+ for (const auto& timeouts : perUidReadTimeouts) {
+ maxPendingTimeUs = std::max(maxPendingTimeUs, microseconds(timeouts.maxPendingTimeUs));
+ }
+ if (maxPendingTimeUs < Constants::minPerUidTimeout) {
+ return;
+ }
+
+ const auto ifs = getIfs(storage);
+ if (!ifs) {
+ return;
+ }
+
+ if (auto err = mIncFs->setUidReadTimeouts(ifs->control, perUidReadTimeouts); err < 0) {
+ LOG(ERROR) << "Setting read timeouts failed: " << -err;
+ return;
+ }
+
+ const auto timeout = std::chrono::duration_cast<milliseconds>(maxPendingTimeUs) -
+ Constants::perUidTimeoutOffset;
+ updateUidReadTimeouts(storage, Clock::now() + timeout);
+}
+
+void IncrementalService::clearUidReadTimeouts(StorageId storage) {
+ const auto ifs = getIfs(storage);
+ if (!ifs) {
+ return;
+ }
+
+ mIncFs->setUidReadTimeouts(ifs->control, {});
+}
+
+void IncrementalService::updateUidReadTimeouts(StorageId storage, Clock::time_point timeLimit) {
+ // Reached maximum timeout.
+ if (Clock::now() >= timeLimit) {
+ return clearUidReadTimeouts(storage);
+ }
+
+ // Still loading?
+ const auto progress = getLoadingProgress(storage);
+ if (progress.isError()) {
+ // Something is wrong, abort.
+ return clearUidReadTimeouts(storage);
+ }
+
+ if (progress.started() && progress.fullyLoaded()) {
+ // Fully loaded, check readLogs collection.
+ const auto ifs = getIfs(storage);
+ if (!ifs->readLogsEnabled()) {
+ return clearUidReadTimeouts(storage);
+ }
+ }
+
+ const auto timeLeft = timeLimit - Clock::now();
+ if (timeLeft < Constants::progressUpdateInterval) {
+ // Don't bother.
+ return clearUidReadTimeouts(storage);
+ }
+
+ addTimedJob(*mTimedQueue, storage, Constants::progressUpdateInterval,
+ [this, storage, timeLimit]() { updateUidReadTimeouts(storage, timeLimit); });
+}
+
std::unordered_set<std::string_view> IncrementalService::adoptMountedInstances() {
std::unordered_set<std::string_view> mountedRootNames;
mIncFs->listExistingMounts([this, &mountedRootNames](auto root, auto backingDir, auto binds) {
@@ -1125,7 +1221,7 @@
// Check if marker file present.
if (checkReadLogsDisabledMarker(root)) {
- ifs->disableReadLogs();
+ ifs->disallowReadLogs();
}
std::vector<std::pair<std::string, metadata::BindPoint>> permanentBindPoints;
@@ -1301,7 +1397,7 @@
// Check if marker file present.
if (checkReadLogsDisabledMarker(mountTarget)) {
- ifs->disableReadLogs();
+ ifs->disallowReadLogs();
}
// DataLoader params
@@ -1705,7 +1801,7 @@
return 0;
}
-int IncrementalService::isFileFullyLoaded(StorageId storage, const std::string& path) const {
+int IncrementalService::isFileFullyLoaded(StorageId storage, std::string_view filePath) const {
std::unique_lock l(mLock);
const auto ifs = getIfsLocked(storage);
if (!ifs) {
@@ -1718,7 +1814,7 @@
return -EINVAL;
}
l.unlock();
- return isFileFullyLoadedFromPath(*ifs, path);
+ return isFileFullyLoadedFromPath(*ifs, filePath);
}
int IncrementalService::isFileFullyLoadedFromPath(const IncFsMount& ifs,
@@ -1736,25 +1832,26 @@
return totalBlocks - filledBlocks;
}
-float IncrementalService::getLoadingProgress(StorageId storage) const {
+IncrementalService::LoadingProgress IncrementalService::getLoadingProgress(
+ StorageId storage) const {
std::unique_lock l(mLock);
const auto ifs = getIfsLocked(storage);
if (!ifs) {
LOG(ERROR) << "getLoadingProgress failed, invalid storageId: " << storage;
- return -EINVAL;
+ return {-EINVAL, -EINVAL};
}
const auto storageInfo = ifs->storages.find(storage);
if (storageInfo == ifs->storages.end()) {
LOG(ERROR) << "getLoadingProgress failed, no storage: " << storage;
- return -EINVAL;
+ return {-EINVAL, -EINVAL};
}
l.unlock();
return getLoadingProgressFromPath(*ifs, storageInfo->second.name);
}
-float IncrementalService::getLoadingProgressFromPath(const IncFsMount& ifs,
- std::string_view storagePath) const {
- size_t totalBlocks = 0, filledBlocks = 0;
+IncrementalService::LoadingProgress IncrementalService::getLoadingProgressFromPath(
+ const IncFsMount& ifs, std::string_view storagePath) const {
+ ssize_t totalBlocks = 0, filledBlocks = 0;
const auto filePaths = mFs->listFilesRecursive(storagePath);
for (const auto& filePath : filePaths) {
const auto [filledBlocksCount, totalBlocksCount] =
@@ -1762,33 +1859,29 @@
if (filledBlocksCount < 0) {
LOG(ERROR) << "getLoadingProgress failed to get filled blocks count for: " << filePath
<< " errno: " << filledBlocksCount;
- return filledBlocksCount;
+ return {filledBlocksCount, filledBlocksCount};
}
totalBlocks += totalBlocksCount;
filledBlocks += filledBlocksCount;
}
- if (totalBlocks == 0) {
- // No file in the storage or files are empty; regarded as fully loaded
- return 1;
- }
- return (float)filledBlocks / (float)totalBlocks;
+ return {filledBlocks, totalBlocks};
}
bool IncrementalService::updateLoadingProgress(
StorageId storage, const StorageLoadingProgressListener& progressListener) {
const auto progress = getLoadingProgress(storage);
- if (progress < 0) {
+ if (progress.isError()) {
// Failed to get progress from incfs, abort.
return false;
}
- progressListener->onStorageLoadingProgressChanged(storage, progress);
- if (progress > 1 - 0.001f) {
+ progressListener->onStorageLoadingProgressChanged(storage, progress.getProgress());
+ if (progress.fullyLoaded()) {
// Stop updating progress once it is fully loaded
return true;
}
- static constexpr auto kProgressUpdateInterval = 1000ms;
- addTimedJob(*mProgressUpdateJobQueue, storage, kProgressUpdateInterval /* repeat after 1s */,
+ addTimedJob(*mProgressUpdateJobQueue, storage,
+ Constants::progressUpdateInterval /* repeat after 1s */,
[storage, progressListener, this]() {
updateLoadingProgress(storage, progressListener);
});
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index eb69470..3066121 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -23,6 +23,7 @@
#include <android/os/incremental/BnIncrementalServiceConnector.h>
#include <android/os/incremental/BnStorageHealthListener.h>
#include <android/os/incremental/BnStorageLoadingProgressListener.h>
+#include <android/os/incremental/PerUidReadTimeouts.h>
#include <android/os/incremental/StorageHealthCheckParams.h>
#include <binder/IAppOpsCallback.h>
#include <utils/String16.h>
@@ -69,6 +70,8 @@
using IStorageLoadingProgressListener = ::android::os::incremental::IStorageLoadingProgressListener;
using StorageLoadingProgressListener = ::android::sp<IStorageLoadingProgressListener>;
+using PerUidReadTimeouts = ::android::os::incremental::PerUidReadTimeouts;
+
class IncrementalService final {
public:
explicit IncrementalService(ServiceManagerWrapper&& sm, std::string_view rootDir);
@@ -98,7 +101,23 @@
};
enum StorageFlags {
- ReadLogsEnabled = 1,
+ ReadLogsAllowed = 1 << 0,
+ ReadLogsEnabled = 1 << 1,
+ };
+
+ struct LoadingProgress {
+ ssize_t filledBlocks;
+ ssize_t totalBlocks;
+
+ bool isError() const { return totalBlocks < 0; }
+ bool started() const { return totalBlocks > 0; }
+ bool fullyLoaded() const { return !isError() && (totalBlocks == filledBlocks); }
+
+ float getProgress() const {
+ return totalBlocks < 0
+ ? totalBlocks
+ : totalBlocks > 0 ? double(filledBlocks) / double(totalBlocks) : 1.f;
+ }
};
static FileId idFromMetadata(std::span<const uint8_t> metadata);
@@ -114,7 +133,8 @@
content::pm::DataLoaderParamsParcel&& dataLoaderParams,
CreateOptions options, const DataLoaderStatusListener& statusListener,
StorageHealthCheckParams&& healthCheckParams,
- const StorageHealthListener& healthListener);
+ const StorageHealthListener& healthListener,
+ const std::vector<PerUidReadTimeouts>& perUidReadTimeouts);
StorageId createLinkedStorage(std::string_view mountPoint, StorageId linkedStorage,
CreateOptions options = CreateOptions::Default);
StorageId openStorage(std::string_view path);
@@ -123,7 +143,7 @@
int unbind(StorageId storage, std::string_view target);
void deleteStorage(StorageId storage);
- void disableReadLogs(StorageId storage);
+ void disallowReadLogs(StorageId storage);
int setStorageParams(StorageId storage, bool enableReadLogs);
int makeFile(StorageId storage, std::string_view path, int mode, FileId id,
@@ -135,8 +155,8 @@
std::string_view newPath);
int unlink(StorageId storage, std::string_view path);
- int isFileFullyLoaded(StorageId storage, const std::string& path) const;
- float getLoadingProgress(StorageId storage) const;
+ int isFileFullyLoaded(StorageId storage, std::string_view filePath) const;
+ LoadingProgress getLoadingProgress(StorageId storage) const;
bool registerLoadingProgressListener(StorageId storage,
const StorageLoadingProgressListener& progressListener);
bool unregisterLoadingProgressListener(StorageId storage);
@@ -282,7 +302,7 @@
const std::string root;
Control control;
/*const*/ MountId mountId;
- int32_t flags = StorageFlags::ReadLogsEnabled;
+ int32_t flags = StorageFlags::ReadLogsAllowed;
StorageMap storages;
BindMap bindPoints;
DataLoaderStubPtr dataLoaderStub;
@@ -301,7 +321,15 @@
StorageMap::iterator makeStorage(StorageId id);
- void disableReadLogs() { flags &= ~StorageFlags::ReadLogsEnabled; }
+ void disallowReadLogs() { flags &= ~StorageFlags::ReadLogsAllowed; }
+ int32_t readLogsAllowed() const { return (flags & StorageFlags::ReadLogsAllowed); }
+
+ void setReadLogsEnabled(bool value) {
+ if (value)
+ flags |= StorageFlags::ReadLogsEnabled;
+ else
+ flags &= ~StorageFlags::ReadLogsEnabled;
+ }
int32_t readLogsEnabled() const { return (flags & StorageFlags::ReadLogsEnabled); }
static void cleanupFilesystem(std::string_view root);
@@ -313,6 +341,11 @@
static bool perfLoggingEnabled();
+ void setUidReadTimeouts(StorageId storage,
+ const std::vector<PerUidReadTimeouts>& perUidReadTimeouts);
+ void clearUidReadTimeouts(StorageId storage);
+ void updateUidReadTimeouts(StorageId storage, Clock::time_point timeLimit);
+
std::unordered_set<std::string_view> adoptMountedInstances();
void mountExistingImages(const std::unordered_set<std::string_view>& mountedRootNames);
bool mountExistingImage(std::string_view root);
@@ -355,7 +388,7 @@
binder::Status applyStorageParams(IncFsMount& ifs, bool enableReadLogs);
int isFileFullyLoadedFromPath(const IncFsMount& ifs, std::string_view filePath) const;
- float getLoadingProgressFromPath(const IncFsMount& ifs, std::string_view path) const;
+ LoadingProgress getLoadingProgressFromPath(const IncFsMount& ifs, std::string_view path) const;
int setFileContent(const IfsMountPtr& ifs, const incfs::FileId& fileId,
std::string_view debugFilePath, std::span<const uint8_t> data) const;
diff --git a/services/incremental/IncrementalServiceValidation.cpp b/services/incremental/IncrementalServiceValidation.cpp
index abadbbf..9f2639a 100644
--- a/services/incremental/IncrementalServiceValidation.cpp
+++ b/services/incremental/IncrementalServiceValidation.cpp
@@ -56,13 +56,18 @@
String16 packageName{package};
- // Caller must also have op granted.
PermissionController pc;
if (auto packageUid = pc.getPackageUid(packageName, 0); packageUid != uid) {
return Exception(binder::Status::EX_SECURITY,
StringPrintf("UID %d / PID %d does not own package %s", uid, pid,
package));
}
+
+ if (!operation) {
+ return binder::Status::ok();
+ }
+
+ // Caller must also have op granted.
switch (auto result = pc.noteOp(String16(operation), uid, packageName); result) {
case PermissionController::MODE_ALLOWED:
case PermissionController::MODE_DEFAULT:
diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp
index dfe9684..b1521b0 100644
--- a/services/incremental/ServiceWrappers.cpp
+++ b/services/incremental/ServiceWrappers.cpp
@@ -206,6 +206,11 @@
std::vector<incfs::ReadInfo>* pendingReadsBuffer) const final {
return incfs::waitForPendingReads(control, timeout, pendingReadsBuffer);
}
+ ErrorCode setUidReadTimeouts(const Control& control,
+ const std::vector<android::os::incremental::PerUidReadTimeouts>&
+ perUidReadTimeouts) const final {
+ return -ENOTSUP;
+ }
};
static JNIEnv* getOrAttachJniEnv(JavaVM* jvm);
diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h
index f2d0073..fad8d67 100644
--- a/services/incremental/ServiceWrappers.h
+++ b/services/incremental/ServiceWrappers.h
@@ -21,6 +21,7 @@
#include <android/content/pm/FileSystemControlParcel.h>
#include <android/content/pm/IDataLoader.h>
#include <android/content/pm/IDataLoaderStatusListener.h>
+#include <android/os/incremental/PerUidReadTimeouts.h>
#include <binder/IAppOpsCallback.h>
#include <binder/IServiceManager.h>
#include <binder/Status.h>
@@ -103,6 +104,10 @@
virtual WaitResult waitForPendingReads(
const Control& control, std::chrono::milliseconds timeout,
std::vector<incfs::ReadInfo>* pendingReadsBuffer) const = 0;
+ virtual ErrorCode setUidReadTimeouts(
+ const Control& control,
+ const std::vector<::android::os::incremental::PerUidReadTimeouts>& perUidReadTimeouts)
+ const = 0;
};
class AppOpsManagerWrapper {
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index 9b8cf40..47b9051 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -42,6 +42,7 @@
using namespace android::incfs;
using namespace android::content::pm;
+using PerUidReadTimeouts = android::os::incremental::PerUidReadTimeouts;
namespace android::os::incremental {
@@ -307,6 +308,9 @@
MOCK_CONST_METHOD3(waitForPendingReads,
WaitResult(const Control& control, std::chrono::milliseconds timeout,
std::vector<incfs::ReadInfo>* pendingReadsBuffer));
+ MOCK_CONST_METHOD2(setUidReadTimeouts,
+ ErrorCode(const Control& control,
+ const std::vector<PerUidReadTimeouts>& perUidReadTimeouts));
MockIncFs() { ON_CALL(*this, listExistingMounts(_)).WillByDefault(Return()); }
@@ -393,6 +397,15 @@
void checkPermissionSuccess() {
ON_CALL(*this, checkPermission(_, _, _)).WillByDefault(Return(android::incremental::Ok()));
}
+ void checkPermissionNoCrossUsers() {
+ ON_CALL(*this,
+ checkPermission("android.permission.LOADER_USAGE_STATS",
+ "android:loader_usage_stats", _))
+ .WillByDefault(Return(android::incremental::Ok()));
+ ON_CALL(*this, checkPermission("android.permission.INTERACT_ACROSS_USERS", nullptr, _))
+ .WillByDefault(
+ Return(android::incremental::Exception(binder::Status::EX_SECURITY, {})));
+ }
void checkPermissionFails() {
ON_CALL(*this, checkPermission(_, _, _))
.WillByDefault(
@@ -665,7 +678,7 @@
TemporaryDir tempDir;
int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
IncrementalService::CreateOptions::CreateNew,
- {}, {}, {});
+ {}, {}, {}, {});
ASSERT_LT(storageId, 0);
}
@@ -676,7 +689,7 @@
TemporaryDir tempDir;
int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
IncrementalService::CreateOptions::CreateNew,
- {}, {}, {});
+ {}, {}, {}, {});
ASSERT_LT(storageId, 0);
}
@@ -689,7 +702,7 @@
TemporaryDir tempDir;
int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
IncrementalService::CreateOptions::CreateNew,
- {}, {}, {});
+ {}, {}, {}, {});
ASSERT_LT(storageId, 0);
}
@@ -703,7 +716,7 @@
TemporaryDir tempDir;
int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
IncrementalService::CreateOptions::CreateNew,
- {}, {}, {});
+ {}, {}, {}, {});
ASSERT_LT(storageId, 0);
}
@@ -721,7 +734,7 @@
TemporaryDir tempDir;
int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
IncrementalService::CreateOptions::CreateNew,
- {}, {}, {});
+ {}, {}, {}, {});
ASSERT_LT(storageId, 0);
}
@@ -735,7 +748,7 @@
TemporaryDir tempDir;
int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
IncrementalService::CreateOptions::CreateNew,
- {}, {}, {});
+ {}, {}, {}, {});
ASSERT_GE(storageId, 0);
mIncrementalService->deleteStorage(storageId);
}
@@ -750,7 +763,7 @@
TemporaryDir tempDir;
int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
IncrementalService::CreateOptions::CreateNew,
- {}, {}, {});
+ {}, {}, {}, {});
ASSERT_GE(storageId, 0);
// Simulated crash/other connection breakage.
mDataLoaderManager->setDataLoaderStatusDestroyed();
@@ -767,7 +780,7 @@
TemporaryDir tempDir;
int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
IncrementalService::CreateOptions::CreateNew,
- {}, {}, {});
+ {}, {}, {}, {});
ASSERT_GE(storageId, 0);
mDataLoaderManager->setDataLoaderStatusCreated();
ASSERT_TRUE(mIncrementalService->startLoading(storageId));
@@ -785,7 +798,7 @@
TemporaryDir tempDir;
int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
IncrementalService::CreateOptions::CreateNew,
- {}, {}, {});
+ {}, {}, {}, {});
ASSERT_GE(storageId, 0);
ASSERT_TRUE(mIncrementalService->startLoading(storageId));
mDataLoaderManager->setDataLoaderStatusCreated();
@@ -802,7 +815,7 @@
TemporaryDir tempDir;
int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
IncrementalService::CreateOptions::CreateNew,
- {}, {}, {});
+ {}, {}, {}, {});
ASSERT_GE(storageId, 0);
mDataLoaderManager->setDataLoaderStatusUnavailable();
}
@@ -823,7 +836,7 @@
TemporaryDir tempDir;
int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
IncrementalService::CreateOptions::CreateNew,
- {}, {}, {});
+ {}, {}, {}, {});
ASSERT_GE(storageId, 0);
mDataLoaderManager->setDataLoaderStatusUnavailable();
ASSERT_NE(nullptr, mLooper->mCallback);
@@ -877,7 +890,7 @@
TemporaryDir tempDir;
int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
IncrementalService::CreateOptions::CreateNew,
- {}, std::move(params), listener);
+ {}, std::move(params), listener, {});
ASSERT_GE(storageId, 0);
// Healthy state, registered for pending reads.
@@ -972,7 +985,7 @@
TemporaryDir tempDir;
int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
IncrementalService::CreateOptions::CreateNew,
- {}, {}, {});
+ {}, {}, {}, {});
ASSERT_GE(storageId, 0);
ASSERT_GE(mDataLoader->setStorageParams(true), 0);
}
@@ -993,11 +1006,11 @@
TemporaryDir tempDir;
int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
IncrementalService::CreateOptions::CreateNew,
- {}, {}, {});
+ {}, {}, {}, {});
ASSERT_GE(storageId, 0);
ASSERT_GE(mDataLoader->setStorageParams(true), 0);
// Now disable.
- mIncrementalService->disableReadLogs(storageId);
+ mIncrementalService->disallowReadLogs(storageId);
ASSERT_EQ(mDataLoader->setStorageParams(true), -EPERM);
}
@@ -1019,7 +1032,7 @@
TemporaryDir tempDir;
int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
IncrementalService::CreateOptions::CreateNew,
- {}, {}, {});
+ {}, {}, {}, {});
ASSERT_GE(storageId, 0);
ASSERT_GE(mDataLoader->setStorageParams(true), 0);
ASSERT_NE(nullptr, mAppOpsManager->mStoredCallback.get());
@@ -1038,7 +1051,24 @@
TemporaryDir tempDir;
int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
IncrementalService::CreateOptions::CreateNew,
- {}, {}, {});
+ {}, {}, {}, {});
+ ASSERT_GE(storageId, 0);
+ ASSERT_LT(mDataLoader->setStorageParams(true), 0);
+}
+
+TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsCheckPermissionNoCrossUsers) {
+ mAppOpsManager->checkPermissionNoCrossUsers();
+
+ EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_));
+ EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+ // checkPermission fails, no calls to set opitions, start or stop WatchingMode.
+ EXPECT_CALL(*mVold, setIncFsMountOptions(_, true)).Times(0);
+ EXPECT_CALL(*mAppOpsManager, startWatchingMode(_, _, _)).Times(0);
+ EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(0);
+ TemporaryDir tempDir;
+ int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew,
+ {}, {}, {}, {});
ASSERT_GE(storageId, 0);
ASSERT_LT(mDataLoader->setStorageParams(true), 0);
}
@@ -1057,7 +1087,7 @@
TemporaryDir tempDir;
int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
IncrementalService::CreateOptions::CreateNew,
- {}, {}, {});
+ {}, {}, {}, {});
ASSERT_GE(storageId, 0);
ASSERT_LT(mDataLoader->setStorageParams(true), 0);
}
@@ -1066,7 +1096,7 @@
TemporaryDir tempDir;
int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
IncrementalService::CreateOptions::CreateNew,
- {}, {}, {});
+ {}, {}, {}, {});
std::string dir_path("test");
// Expecting incfs to call makeDir on a path like:
@@ -1085,7 +1115,7 @@
TemporaryDir tempDir;
int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
IncrementalService::CreateOptions::CreateNew,
- {}, {}, {});
+ {}, {}, {}, {});
auto first = "first"sv;
auto second = "second"sv;
auto third = "third"sv;
@@ -1108,7 +1138,7 @@
TemporaryDir tempDir;
int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
IncrementalService::CreateOptions::CreateNew,
- {}, {}, {});
+ {}, {}, {}, {});
ASSERT_EQ(-1, mIncrementalService->isFileFullyLoaded(storageId, "base.apk"));
}
@@ -1119,7 +1149,7 @@
TemporaryDir tempDir;
int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
IncrementalService::CreateOptions::CreateNew,
- {}, {}, {});
+ {}, {}, {}, {});
EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(1);
ASSERT_EQ(-1, mIncrementalService->isFileFullyLoaded(storageId, "base.apk"));
}
@@ -1131,7 +1161,7 @@
TemporaryDir tempDir;
int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
IncrementalService::CreateOptions::CreateNew,
- {}, {}, {});
+ {}, {}, {}, {});
EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(1);
ASSERT_EQ(0, mIncrementalService->isFileFullyLoaded(storageId, "base.apk"));
}
@@ -1143,7 +1173,7 @@
TemporaryDir tempDir;
int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
IncrementalService::CreateOptions::CreateNew,
- {}, {}, {});
+ {}, {}, {}, {});
EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(1);
ASSERT_EQ(0, mIncrementalService->isFileFullyLoaded(storageId, "base.apk"));
}
@@ -1155,8 +1185,8 @@
TemporaryDir tempDir;
int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
IncrementalService::CreateOptions::CreateNew,
- {}, {}, {});
- ASSERT_EQ(1, mIncrementalService->getLoadingProgress(storageId));
+ {}, {}, {}, {});
+ ASSERT_EQ(1, mIncrementalService->getLoadingProgress(storageId).getProgress());
}
TEST_F(IncrementalServiceTest, testGetLoadingProgressFailsWithFailedRanges) {
@@ -1166,9 +1196,9 @@
TemporaryDir tempDir;
int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
IncrementalService::CreateOptions::CreateNew,
- {}, {}, {});
+ {}, {}, {}, {});
EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(1);
- ASSERT_EQ(-1, mIncrementalService->getLoadingProgress(storageId));
+ ASSERT_EQ(-1, mIncrementalService->getLoadingProgress(storageId).getProgress());
}
TEST_F(IncrementalServiceTest, testGetLoadingProgressSuccessWithEmptyRanges) {
@@ -1178,9 +1208,9 @@
TemporaryDir tempDir;
int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
IncrementalService::CreateOptions::CreateNew,
- {}, {}, {});
+ {}, {}, {}, {});
EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(3);
- ASSERT_EQ(1, mIncrementalService->getLoadingProgress(storageId));
+ ASSERT_EQ(1, mIncrementalService->getLoadingProgress(storageId).getProgress());
}
TEST_F(IncrementalServiceTest, testGetLoadingProgressSuccess) {
@@ -1190,9 +1220,9 @@
TemporaryDir tempDir;
int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
IncrementalService::CreateOptions::CreateNew,
- {}, {}, {});
+ {}, {}, {}, {});
EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(3);
- ASSERT_EQ(0.5, mIncrementalService->getLoadingProgress(storageId));
+ ASSERT_EQ(0.5, mIncrementalService->getLoadingProgress(storageId).getProgress());
}
TEST_F(IncrementalServiceTest, testRegisterLoadingProgressListenerSuccess) {
@@ -1202,7 +1232,7 @@
TemporaryDir tempDir;
int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
IncrementalService::CreateOptions::CreateNew,
- {}, {}, {});
+ {}, {}, {}, {});
sp<NiceMock<MockStorageLoadingProgressListener>> listener{
new NiceMock<MockStorageLoadingProgressListener>};
NiceMock<MockStorageLoadingProgressListener>* listenerMock = listener.get();
@@ -1227,7 +1257,7 @@
TemporaryDir tempDir;
int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
IncrementalService::CreateOptions::CreateNew,
- {}, {}, {});
+ {}, {}, {}, {});
sp<NiceMock<MockStorageLoadingProgressListener>> listener{
new NiceMock<MockStorageLoadingProgressListener>};
NiceMock<MockStorageLoadingProgressListener>* listenerMock = listener.get();
@@ -1242,9 +1272,10 @@
NiceMock<MockStorageHealthListener>* newListenerMock = newListener.get();
TemporaryDir tempDir;
- int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
- IncrementalService::CreateOptions::CreateNew,
- {}, StorageHealthCheckParams{}, listener);
+ int storageId =
+ mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew, {},
+ StorageHealthCheckParams{}, listener, {});
ASSERT_GE(storageId, 0);
StorageHealthCheckParams newParams;
newParams.blockedTimeoutMs = 10000;
@@ -1313,4 +1344,123 @@
mTimedQueue->clearJob(storageId);
}
+static std::vector<PerUidReadTimeouts> createPerUidTimeouts(
+ std::initializer_list<std::tuple<int, int, int, int>> tuples) {
+ std::vector<PerUidReadTimeouts> result;
+ for (auto&& tuple : tuples) {
+ result.emplace_back();
+ auto& timeouts = result.back();
+ timeouts.uid = std::get<0>(tuple);
+ timeouts.minTimeUs = std::get<1>(tuple);
+ timeouts.minPendingTimeUs = std::get<2>(tuple);
+ timeouts.maxPendingTimeUs = std::get<3>(tuple);
+ }
+ return result;
+}
+
+static ErrorCode checkPerUidTimeouts(const Control& control,
+ const std::vector<PerUidReadTimeouts>& perUidReadTimeouts) {
+ std::vector<PerUidReadTimeouts> expected =
+ createPerUidTimeouts({{0, 1, 2, 3}, {1, 2, 3, 4}, {2, 3, 4, 100000000}});
+ EXPECT_EQ(expected, perUidReadTimeouts);
+ return 0;
+}
+
+static ErrorCode checkPerUidTimeoutsEmpty(
+ const Control& control, const std::vector<PerUidReadTimeouts>& perUidReadTimeouts) {
+ EXPECT_EQ(0u, perUidReadTimeouts.size());
+ return 0;
+}
+
+TEST_F(IncrementalServiceTest, testPerUidTimeoutsTooShort) {
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
+ EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
+ EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
+ EXPECT_CALL(*mDataLoader, start(_)).Times(0);
+ EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
+ EXPECT_CALL(*mIncFs, setUidReadTimeouts(_, _)).Times(0);
+ EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(0);
+ EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+ TemporaryDir tempDir;
+ int storageId =
+ mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew, {}, {},
+ {},
+ createPerUidTimeouts(
+ {{0, 1, 2, 3}, {1, 2, 3, 4}, {2, 3, 4, 5}}));
+ ASSERT_GE(storageId, 0);
+}
+
+TEST_F(IncrementalServiceTest, testPerUidTimeoutsSuccess) {
+ mVold->setIncFsMountOptionsSuccess();
+ mAppOpsManager->checkPermissionSuccess();
+ mFs->hasFiles();
+
+ EXPECT_CALL(*mIncFs, setUidReadTimeouts(_, _))
+ // First call.
+ .WillOnce(Invoke(&checkPerUidTimeouts))
+ // Fully loaded and no readlogs.
+ .WillOnce(Invoke(&checkPerUidTimeoutsEmpty));
+ EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(3);
+
+ // Empty storage.
+ mIncFs->countFilledBlocksEmpty();
+
+ TemporaryDir tempDir;
+ int storageId =
+ mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew, {}, {},
+ {},
+ createPerUidTimeouts({{0, 1, 2, 3},
+ {1, 2, 3, 4},
+ {2, 3, 4, 100000000}}));
+ ASSERT_GE(storageId, 0);
+
+ {
+ // Timed callback present -> 0 progress.
+ ASSERT_EQ(storageId, mTimedQueue->mId);
+ ASSERT_GE(mTimedQueue->mAfter, std::chrono::seconds(1));
+ const auto timedCallback = mTimedQueue->mWhat;
+ mTimedQueue->clearJob(storageId);
+
+ // Still loading.
+ mIncFs->countFilledBlocksSuccess();
+
+ // Call it again.
+ timedCallback();
+ }
+
+ {
+ // Still present -> 0.5 progress.
+ ASSERT_EQ(storageId, mTimedQueue->mId);
+ ASSERT_GE(mTimedQueue->mAfter, std::chrono::seconds(1));
+ const auto timedCallback = mTimedQueue->mWhat;
+ mTimedQueue->clearJob(storageId);
+
+ // Fully loaded but readlogs collection enabled.
+ mIncFs->countFilledBlocksFullyLoaded();
+ ASSERT_GE(mDataLoader->setStorageParams(true), 0);
+
+ // Call it again.
+ timedCallback();
+ }
+
+ {
+ // Still present -> fully loaded + readlogs.
+ ASSERT_EQ(storageId, mTimedQueue->mId);
+ ASSERT_GE(mTimedQueue->mAfter, std::chrono::seconds(1));
+ const auto timedCallback = mTimedQueue->mWhat;
+ mTimedQueue->clearJob(storageId);
+
+ // Now disable readlogs.
+ ASSERT_GE(mDataLoader->setStorageParams(false), 0);
+
+ // Call it again.
+ timedCallback();
+ }
+
+ // No callbacks anymore -> fully loaded and no readlogs.
+ ASSERT_EQ(mTimedQueue->mAfter, Milliseconds());
+}
+
} // namespace android::os::incremental
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 4d15ced..eb7162f 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -237,6 +237,8 @@
"com.android.server.companion.CompanionDeviceManagerService";
private static final String STATS_COMPANION_APEX_PATH =
"/apex/com.android.os.statsd/javalib/service-statsd.jar";
+ private static final String CONNECTIVITY_SERVICE_APEX_PATH =
+ "/apex/com.android.tethering/javalib/service-connectivity.jar";
private static final String STATS_COMPANION_LIFECYCLE_CLASS =
"com.android.server.stats.StatsCompanion$Lifecycle";
private static final String STATS_PULL_ATOM_SERVICE_CLASS =
@@ -297,6 +299,8 @@
"com.android.server.autofill.AutofillManagerService";
private static final String CONTENT_CAPTURE_MANAGER_SERVICE_CLASS =
"com.android.server.contentcapture.ContentCaptureManagerService";
+ private static final String TRANSLATION_MANAGER_SERVICE_CLASS =
+ "com.android.server.translation.TranslationManagerService";
private static final String MUSIC_RECOGNITION_MANAGER_SERVICE_CLASS =
"com.android.server.musicrecognition.MusicRecognitionManagerService";
private static final String SYSTEM_CAPTIONS_MANAGER_SERVICE_CLASS =
@@ -1356,7 +1360,8 @@
}
t.traceBegin("IpConnectivityMetrics");
- mSystemServiceManager.startService(IP_CONNECTIVITY_METRICS_CLASS);
+ mSystemServiceManager.startServiceFromJar(IP_CONNECTIVITY_METRICS_CLASS,
+ CONNECTIVITY_SERVICE_APEX_PATH);
t.traceEnd();
t.traceBegin("NetworkWatchlistService");
@@ -1721,8 +1726,8 @@
// This has to be called after NetworkManagementService, NetworkStatsService
// and NetworkPolicyManager because ConnectivityService needs to take these
// services to initialize.
- // TODO: Dynamically load service-connectivity.jar by using startServiceFromJar.
- mSystemServiceManager.startService(CONNECTIVITY_SERVICE_INITIALIZER_CLASS);
+ mSystemServiceManager.startServiceFromJar(CONNECTIVITY_SERVICE_INITIALIZER_CLASS,
+ CONNECTIVITY_SERVICE_APEX_PATH);
connectivity = IConnectivityManager.Stub.asInterface(
ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
// TODO: Use ConnectivityManager instead of ConnectivityService.
@@ -2288,6 +2293,13 @@
t.traceEnd();
}
+ // Translation manager service
+ if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_TRANSLATION)) {
+ t.traceBegin("StartTranslationManagerService");
+ mSystemServiceManager.startService(TRANSLATION_MANAGER_SERVICE_CLASS);
+ t.traceEnd();
+ }
+
// NOTE: ClipboardService depends on ContentCapture and Autofill
t.traceBegin("StartClipboardService");
mSystemServiceManager.startService(ClipboardService.class);
diff --git a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
index 3220dff..a691a8d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
@@ -313,29 +313,30 @@
}
}
- private static final int NONE = 0;
- private static final int ALARMS_ONLY = 1 << 0;
- private static final int JOBS_ONLY = 1 << 1;
- private static final int JOBS_AND_ALARMS = ALARMS_ONLY | JOBS_ONLY;
-
- private void areRestricted(AppStateTrackerTestable instance, int uid, String packageName,
- int restrictionTypes, boolean exemptFromBatterySaver) {
- assertEquals(((restrictionTypes & JOBS_ONLY) != 0),
- instance.areJobsRestricted(uid, packageName, exemptFromBatterySaver));
- assertEquals(((restrictionTypes & ALARMS_ONLY) != 0),
- instance.areAlarmsRestricted(uid, packageName, exemptFromBatterySaver));
+ private void areJobsRestricted(AppStateTrackerTestable instance, int[] uids, String[] packages,
+ boolean[] restricted, boolean exemption) {
+ assertTrue(uids.length == packages.length && uids.length == restricted.length);
+ for (int i = 0; i < uids.length; i++) {
+ assertEquals(restricted[i],
+ instance.areJobsRestricted(uids[i], packages[i], exemption));
+ }
}
- private void areRestricted(AppStateTrackerTestable instance, int uid, String packageName,
- int restrictionTypes) {
- areRestricted(instance, uid, packageName, restrictionTypes,
- /*exemptFromBatterySaver=*/ false);
+ private void areAlarmsRestrictedByFAS(AppStateTrackerTestable instance, int[] uids,
+ String[] packages, boolean[] restricted) {
+ assertTrue(uids.length == packages.length && uids.length == restricted.length);
+ for (int i = 0; i < uids.length; i++) {
+ assertEquals(restricted[i], instance.areAlarmsRestricted(uids[i], packages[i]));
+ }
}
- private void areRestrictedWithExemption(AppStateTrackerTestable instance,
- int uid, String packageName, int restrictionTypes) {
- areRestricted(instance, uid, packageName, restrictionTypes,
- /*exemptFromBatterySaver=*/ true);
+ private void areAlarmsRestrictedByBatterySaver(AppStateTrackerTestable instance, int[] uids,
+ String[] packages, boolean[] restricted) {
+ assertTrue(uids.length == packages.length && uids.length == restricted.length);
+ for (int i = 0; i < uids.length; i++) {
+ assertEquals(restricted[i],
+ instance.areAlarmsRestrictedByBatterySaver(uids[i], packages[i]));
+ }
}
@Test
@@ -344,30 +345,42 @@
callStart(instance);
assertFalse(instance.isForceAllAppsStandbyEnabled());
- areRestricted(instance, UID_1, PACKAGE_1, NONE);
- areRestricted(instance, UID_2, PACKAGE_2, NONE);
- areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
-
- areRestrictedWithExemption(instance, UID_1, PACKAGE_1, NONE);
- areRestrictedWithExemption(instance, UID_2, PACKAGE_2, NONE);
- areRestrictedWithExemption(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {false, false, false, false},
+ false);
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {false, false, false, false},
+ true);
+ areAlarmsRestrictedByBatterySaver(instance,
+ new int[] {UID_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {false, false, false, false});
mPowerSaveMode = true;
mPowerSaveObserver.accept(getPowerSaveState());
assertTrue(instance.isForceAllAppsStandbyEnabled());
- areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS);
- areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
- areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
-
- areRestrictedWithExemption(instance, UID_1, PACKAGE_1, NONE);
- areRestrictedWithExemption(instance, UID_2, PACKAGE_2, NONE);
- areRestrictedWithExemption(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {true, true, true, false},
+ false);
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {false, false, false, false},
+ true);
+ areAlarmsRestrictedByBatterySaver(instance,
+ new int[] {UID_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {true, true, true, false});
// Toggle the foreground state.
- mPowerSaveMode = true;
- mPowerSaveObserver.accept(getPowerSaveState());
assertFalse(instance.isUidActive(UID_1));
assertFalse(instance.isUidActive(UID_2));
@@ -376,34 +389,65 @@
mIUidObserver.onUidActive(UID_1);
waitUntilMainHandlerDrain();
waitUntilMainHandlerDrain();
- areRestricted(instance, UID_1, PACKAGE_1, NONE);
- areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
- areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {false, true, true, false},
+ false);
+ areAlarmsRestrictedByBatterySaver(instance,
+ new int[] {UID_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {false, true, true, false});
+
assertTrue(instance.isUidActive(UID_1));
assertFalse(instance.isUidActive(UID_2));
mIUidObserver.onUidGone(UID_1, /*disable=*/ false);
waitUntilMainHandlerDrain();
waitUntilMainHandlerDrain();
- areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS);
- areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
- areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {true, true, true, false},
+ false);
+ areAlarmsRestrictedByBatterySaver(instance,
+ new int[] {UID_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {true, true, true, false});
+
assertFalse(instance.isUidActive(UID_1));
assertFalse(instance.isUidActive(UID_2));
mIUidObserver.onUidActive(UID_1);
waitUntilMainHandlerDrain();
waitUntilMainHandlerDrain();
- areRestricted(instance, UID_1, PACKAGE_1, NONE);
- areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
- areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {false, true, true, false},
+ false);
+ areAlarmsRestrictedByBatterySaver(instance,
+ new int[] {UID_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {false, true, true, false});
mIUidObserver.onUidIdle(UID_1, /*disable=*/ false);
waitUntilMainHandlerDrain();
waitUntilMainHandlerDrain();
- areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS);
- areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
- areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {true, true, true, false},
+ false);
+ areAlarmsRestrictedByBatterySaver(instance,
+ new int[] {UID_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {true, true, true, false});
+
assertFalse(instance.isUidActive(UID_1));
assertFalse(instance.isUidActive(UID_2));
@@ -416,11 +460,19 @@
assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_2, PACKAGE_2));
assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_2, PACKAGE_2));
- areRestricted(instance, UID_1, PACKAGE_1, NONE);
- areRestricted(instance, UID_10_1, PACKAGE_1, NONE);
- areRestricted(instance, UID_2, PACKAGE_2, NONE);
- areRestricted(instance, UID_10_2, PACKAGE_2, NONE);
- areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {false, false, false, false, false},
+ false);
+ areAlarmsRestrictedByBatterySaver(instance,
+ new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {false, false, false, false, false});
+ areAlarmsRestrictedByFAS(instance,
+ new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {false, false, false, false, false});
setAppOps(UID_1, PACKAGE_1, true);
setAppOps(UID_10_2, PACKAGE_2, true);
@@ -429,24 +481,72 @@
assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_2, PACKAGE_2));
assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_2, PACKAGE_2));
- areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS);
- areRestricted(instance, UID_10_1, PACKAGE_1, NONE);
- areRestricted(instance, UID_2, PACKAGE_2, NONE);
- areRestricted(instance, UID_10_2, PACKAGE_2, JOBS_AND_ALARMS);
- areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {true, false, false, true, false},
+ false);
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {true, false, false, true, false},
+ true);
+
+ areAlarmsRestrictedByBatterySaver(instance,
+ new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {false, false, false, false, false});
+ areAlarmsRestrictedByFAS(instance,
+ new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {true, false, false, true, false});
// Toggle power saver, should still be the same.
mPowerSaveMode = true;
mPowerSaveObserver.accept(getPowerSaveState());
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {true, true, true, true, false},
+ false);
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {true, false, false, true, false},
+ true);
+
+ areAlarmsRestrictedByBatterySaver(instance,
+ new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {true, true, true, true, false});
+ areAlarmsRestrictedByFAS(instance,
+ new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {true, false, false, true, false});
+
mPowerSaveMode = false;
mPowerSaveObserver.accept(getPowerSaveState());
- areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS);
- areRestricted(instance, UID_10_1, PACKAGE_1, NONE);
- areRestricted(instance, UID_2, PACKAGE_2, NONE);
- areRestricted(instance, UID_10_2, PACKAGE_2, JOBS_AND_ALARMS);
- areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {true, false, false, true, false},
+ false);
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {true, false, false, true, false},
+ true);
+
+ areAlarmsRestrictedByBatterySaver(instance,
+ new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {false, false, false, false, false});
+ areAlarmsRestrictedByFAS(instance,
+ new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {true, false, false, true, false});
// Clear the app ops and update the exemption list.
setAppOps(UID_1, PACKAGE_1, false);
@@ -455,24 +555,41 @@
mPowerSaveMode = true;
mPowerSaveObserver.accept(getPowerSaveState());
- areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS);
- areRestricted(instance, UID_10_1, PACKAGE_1, JOBS_AND_ALARMS);
- areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
- areRestricted(instance, UID_10_2, PACKAGE_2, JOBS_AND_ALARMS);
- areRestricted(instance, UID_3, PACKAGE_3, JOBS_AND_ALARMS);
- areRestricted(instance, UID_10_3, PACKAGE_3, JOBS_AND_ALARMS);
- areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {true, true, true, true, false},
+ false);
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {false, false, false, false, false},
+ true);
+
+ areAlarmsRestrictedByBatterySaver(instance,
+ new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {true, true, true, true, false});
+ areAlarmsRestrictedByFAS(instance,
+ new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {false, false, false, false, false});
instance.setPowerSaveExemptionListAppIds(new int[] {UID_1}, new int[] {},
new int[] {UID_2});
- areRestricted(instance, UID_1, PACKAGE_1, NONE);
- areRestricted(instance, UID_10_1, PACKAGE_1, NONE);
- areRestricted(instance, UID_2, PACKAGE_2, ALARMS_ONLY);
- areRestricted(instance, UID_10_2, PACKAGE_2, ALARMS_ONLY);
- areRestricted(instance, UID_3, PACKAGE_3, JOBS_AND_ALARMS);
- areRestricted(instance, UID_10_3, PACKAGE_3, JOBS_AND_ALARMS);
- areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_10_1, UID_2, UID_10_2, UID_3, UID_10_3, Process.SYSTEM_UID},
+ new String[]{PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_3, PACKAGE_3,
+ PACKAGE_SYSTEM},
+ new boolean[] {false, false, false, false, true, true, false},
+ false);
+
+ areAlarmsRestrictedByBatterySaver(instance,
+ new int[] {UID_1, UID_10_1, UID_2, UID_10_2, UID_3, UID_10_3, Process.SYSTEM_UID},
+ new String[]{PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_3, PACKAGE_3,
+ PACKAGE_SYSTEM},
+ new boolean[] {false, false, true, true, true, true, false});
// Again, make sure toggling the global state doesn't change it.
mPowerSaveMode = false;
@@ -481,13 +598,18 @@
mPowerSaveMode = true;
mPowerSaveObserver.accept(getPowerSaveState());
- areRestricted(instance, UID_1, PACKAGE_1, NONE);
- areRestricted(instance, UID_10_1, PACKAGE_1, NONE);
- areRestricted(instance, UID_2, PACKAGE_2, ALARMS_ONLY);
- areRestricted(instance, UID_10_2, PACKAGE_2, ALARMS_ONLY);
- areRestricted(instance, UID_3, PACKAGE_3, JOBS_AND_ALARMS);
- areRestricted(instance, UID_10_3, PACKAGE_3, JOBS_AND_ALARMS);
- areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_10_1, UID_2, UID_10_2, UID_3, UID_10_3, Process.SYSTEM_UID},
+ new String[]{PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_3, PACKAGE_3,
+ PACKAGE_SYSTEM},
+ new boolean[] {false, false, false, false, true, true, false},
+ false);
+
+ areAlarmsRestrictedByBatterySaver(instance,
+ new int[] {UID_1, UID_10_1, UID_2, UID_10_2, UID_3, UID_10_3, Process.SYSTEM_UID},
+ new String[]{PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_3, PACKAGE_3,
+ PACKAGE_SYSTEM},
+ new boolean[] {false, false, true, true, true, true, false});
assertTrue(instance.isUidPowerSaveExempt(UID_1));
assertTrue(instance.isUidPowerSaveExempt(UID_10_1));
@@ -646,52 +768,98 @@
}
@Test
- public void testExempt() throws Exception {
+ public void testExemptedBucket() throws Exception {
final AppStateTrackerTestable instance = newInstance();
callStart(instance);
assertFalse(instance.isForceAllAppsStandbyEnabled());
- areRestricted(instance, UID_1, PACKAGE_1, NONE);
- areRestricted(instance, UID_2, PACKAGE_2, NONE);
- areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {false, false, false},
+ false);
+ areAlarmsRestrictedByBatterySaver(instance,
+ new int[] {UID_1, UID_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {false, false, false});
mPowerSaveMode = true;
mPowerSaveObserver.accept(getPowerSaveState());
assertTrue(instance.isForceAllAppsStandbyEnabled());
- areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS);
- areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
- areRestricted(instance, UID_10_2, PACKAGE_2, JOBS_AND_ALARMS);
- areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {true, true, true, false},
+ false);
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {false, false, false, false},
+ true);
+ areAlarmsRestrictedByBatterySaver(instance,
+ new int[] {UID_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {true, true, true, false});
// Exempt package 2 on user-10.
mAppIdleStateChangeListener.onAppIdleStateChanged(PACKAGE_2, /*user=*/ 10, false,
UsageStatsManager.STANDBY_BUCKET_EXEMPTED, REASON_MAIN_DEFAULT);
- areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS);
- areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
- areRestricted(instance, UID_10_2, PACKAGE_2, NONE);
-
- areRestrictedWithExemption(instance, UID_1, PACKAGE_1, NONE);
- areRestrictedWithExemption(instance, UID_2, PACKAGE_2, NONE);
- areRestrictedWithExemption(instance, UID_10_2, PACKAGE_2, NONE);
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_2, UID_10_2},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2},
+ new boolean[] {true, true, false},
+ false);
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_2, UID_10_2},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2},
+ new boolean[] {false, false, false},
+ true);
+ areAlarmsRestrictedByBatterySaver(instance,
+ new int[] {UID_1, UID_2, UID_10_2},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2},
+ new boolean[] {true, true, false});
// Exempt package 1 on user-0.
mAppIdleStateChangeListener.onAppIdleStateChanged(PACKAGE_1, /*user=*/ 0, false,
UsageStatsManager.STANDBY_BUCKET_EXEMPTED, REASON_MAIN_DEFAULT);
- areRestricted(instance, UID_1, PACKAGE_1, NONE);
- areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
- areRestricted(instance, UID_10_2, PACKAGE_2, NONE);
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_2, UID_10_2},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2},
+ new boolean[] {false, true, false},
+ false);
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_2, UID_10_2},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2},
+ new boolean[] {false, false, false},
+ true);
+ areAlarmsRestrictedByBatterySaver(instance,
+ new int[] {UID_1, UID_2, UID_10_2},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2},
+ new boolean[] {false, true, false});
// Unexempt package 2 on user-10.
mAppIdleStateChangeListener.onAppIdleStateChanged(PACKAGE_2, /*user=*/ 10, false,
UsageStatsManager.STANDBY_BUCKET_ACTIVE, REASON_MAIN_USAGE);
- areRestricted(instance, UID_1, PACKAGE_1, NONE);
- areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
- areRestricted(instance, UID_10_2, PACKAGE_2, JOBS_AND_ALARMS);
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_2, UID_10_2},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2},
+ new boolean[] {false, true, true},
+ false);
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_2, UID_10_2},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2},
+ new boolean[] {false, false, false},
+ true);
+ areAlarmsRestrictedByBatterySaver(instance,
+ new int[] {UID_1, UID_2, UID_10_2},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2},
+ new boolean[] {false, true, true});
// Check force-app-standby.
// EXEMPT doesn't exempt from force-app-standby.
@@ -703,13 +871,28 @@
mAppIdleStateChangeListener.onAppIdleStateChanged(PACKAGE_2, /*user=*/ 0, false,
UsageStatsManager.STANDBY_BUCKET_EXEMPTED, REASON_MAIN_DEFAULT);
+ // All 3 packages (u0:p1, u0:p2, u10:p2) are now in the exempted bucket.
setAppOps(UID_1, PACKAGE_1, true);
- areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS);
- areRestricted(instance, UID_2, PACKAGE_2, NONE);
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_2, UID_10_2},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2},
+ new boolean[] {true, false, false},
+ false);
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_2, UID_10_2},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2},
+ new boolean[] {true, false, false},
+ true);
- areRestrictedWithExemption(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS);
- areRestrictedWithExemption(instance, UID_2, PACKAGE_2, NONE);
+ areAlarmsRestrictedByBatterySaver(instance,
+ new int[] {UID_1, UID_2, UID_10_2},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2},
+ new boolean[] {false, false, false});
+ areAlarmsRestrictedByFAS(instance,
+ new int[] {UID_1, UID_2, UID_10_2},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2},
+ new boolean[] {true, false, false});
}
@Test
@@ -809,6 +992,8 @@
verify(l, times(0)).updateJobsForUid(anyInt(), anyBoolean());
verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+ verify(l, times(1)).updateAllAlarms();
+ verify(l, times(0)).updateAlarmsForUid(anyInt());
verify(l, times(0)).unblockAllUnrestrictedAlarms();
verify(l, times(0)).unblockAlarmsForUid(anyInt());
verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
@@ -823,7 +1008,9 @@
verify(l, times(0)).updateJobsForUid(anyInt(), anyBoolean());
verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
- verify(l, times(1)).unblockAllUnrestrictedAlarms();
+ verify(l, times(1)).updateAllAlarms();
+ verify(l, times(0)).updateAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
verify(l, times(0)).unblockAlarmsForUid(anyInt());
verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
reset(l);
@@ -853,6 +1040,8 @@
verify(l, times(0)).updateJobsForUid(anyInt(), anyBoolean());
verify(l, times(1)).updateJobsForUidPackage(eq(UID_10_2), eq(PACKAGE_2), anyBoolean());
+ verify(l, times(0)).updateAllAlarms();
+ verify(l, times(0)).updateAlarmsForUid(anyInt());
verify(l, times(0)).unblockAllUnrestrictedAlarms();
verify(l, times(0)).unblockAlarmsForUid(anyInt());
verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
@@ -865,6 +1054,8 @@
verify(l, times(0)).updateJobsForUid(anyInt(), anyBoolean());
verify(l, times(1)).updateJobsForUidPackage(eq(UID_10_2), eq(PACKAGE_2), anyBoolean());
+ verify(l, times(0)).updateAllAlarms();
+ verify(l, times(0)).updateAlarmsForUid(anyInt());
verify(l, times(0)).unblockAllUnrestrictedAlarms();
verify(l, times(0)).unblockAlarmsForUid(anyInt());
verify(l, times(1)).unblockAlarmsForUidPackage(eq(UID_10_2), eq(PACKAGE_2));
@@ -876,15 +1067,16 @@
verify(l, times(0)).updateJobsForUid(anyInt(), anyBoolean());
verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+ verify(l, times(0)).updateAllAlarms();
+ verify(l, times(0)).updateAlarmsForUid(anyInt());
verify(l, times(0)).unblockAllUnrestrictedAlarms();
verify(l, times(0)).unblockAlarmsForUid(anyInt());
verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
- // Unrestrict while battery saver is on. Shouldn't fire.
+ // Test overlap with battery saver
mPowerSaveMode = true;
mPowerSaveObserver.accept(getPowerSaveState());
- // Note toggling appops while BS is on will suppress unblockAlarmsForUidPackage().
setAppOps(UID_10_2, PACKAGE_2, true);
waitUntilMainHandlerDrain();
@@ -892,6 +1084,8 @@
verify(l, times(0)).updateJobsForUid(anyInt(), anyBoolean());
verify(l, times(1)).updateJobsForUidPackage(eq(UID_10_2), eq(PACKAGE_2), anyBoolean());
+ verify(l, times(1)).updateAllAlarms();
+ verify(l, times(0)).updateAlarmsForUid(anyInt());
verify(l, times(0)).unblockAllUnrestrictedAlarms();
verify(l, times(0)).unblockAlarmsForUid(anyInt());
verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
@@ -906,7 +1100,9 @@
verify(l, times(0)).updateJobsForUid(anyInt(), anyBoolean());
verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
- verify(l, times(1)).unblockAllUnrestrictedAlarms();
+ verify(l, times(1)).updateAllAlarms();
+ verify(l, times(0)).updateAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
verify(l, times(0)).unblockAlarmsForUid(anyInt());
verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
reset(l);
@@ -922,7 +1118,9 @@
verify(l, times(0)).updateJobsForUid(anyInt(), anyBoolean());
verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
- verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(1)).updateAllAlarms();
+ verify(l, times(0)).updateAlarmsForUid(anyInt());
+ verify(l, times(1)).unblockAllUnrestrictedAlarms();
verify(l, times(0)).unblockAlarmsForUid(anyInt());
verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
reset(l);
@@ -934,7 +1132,9 @@
verify(l, times(0)).updateJobsForUid(anyInt(), anyBoolean());
verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
- verify(l, times(1)).unblockAllUnrestrictedAlarms();
+ verify(l, times(1)).updateAllAlarms();
+ verify(l, times(0)).updateAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
verify(l, times(0)).unblockAlarmsForUid(anyInt());
verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
reset(l);
@@ -948,6 +1148,8 @@
verify(l, times(0)).updateJobsForUid(anyInt(), anyBoolean());
verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+ verify(l, times(0)).updateAllAlarms();
+ verify(l, times(0)).updateAlarmsForUid(anyInt());
verify(l, times(0)).unblockAllUnrestrictedAlarms();
verify(l, times(0)).unblockAlarmsForUid(anyInt());
verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
@@ -961,12 +1163,14 @@
verify(l, times(0)).updateJobsForUid(anyInt(), anyBoolean());
verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+ verify(l, times(0)).updateAllAlarms();
+ verify(l, times(0)).updateAlarmsForUid(anyInt());
verify(l, times(0)).unblockAllUnrestrictedAlarms();
verify(l, times(0)).unblockAlarmsForUid(anyInt());
verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
reset(l);
- // Do the same thing with battery saver on. (Currently same callbacks are called.)
+ // Do the same thing with battery saver on.
mPowerSaveMode = true;
mPowerSaveObserver.accept(getPowerSaveState());
@@ -975,6 +1179,8 @@
verify(l, times(0)).updateJobsForUid(anyInt(), anyBoolean());
verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+ verify(l, times(1)).updateAllAlarms();
+ verify(l, times(0)).updateAlarmsForUid(anyInt());
verify(l, times(0)).unblockAllUnrestrictedAlarms();
verify(l, times(0)).unblockAlarmsForUid(anyInt());
verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
@@ -989,7 +1195,9 @@
verify(l, times(0)).updateJobsForUid(anyInt(), anyBoolean());
verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
- verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(1)).updateAllAlarms();
+ verify(l, times(0)).updateAlarmsForUid(anyInt());
+ verify(l, times(1)).unblockAllUnrestrictedAlarms();
verify(l, times(0)).unblockAlarmsForUid(anyInt());
verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
reset(l);
@@ -1001,7 +1209,9 @@
verify(l, times(0)).updateJobsForUid(anyInt(), anyBoolean());
verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
- verify(l, times(1)).unblockAllUnrestrictedAlarms();
+ verify(l, times(1)).updateAllAlarms();
+ verify(l, times(0)).updateAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
verify(l, times(0)).unblockAlarmsForUid(anyInt());
verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
reset(l);
@@ -1015,6 +1225,8 @@
verify(l, times(0)).updateJobsForUid(anyInt(), anyBoolean());
verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+ verify(l, times(0)).updateAllAlarms();
+ verify(l, times(0)).updateAlarmsForUid(anyInt());
verify(l, times(0)).unblockAllUnrestrictedAlarms();
verify(l, times(0)).unblockAlarmsForUid(anyInt());
verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
@@ -1028,6 +1240,8 @@
verify(l, times(0)).updateJobsForUid(anyInt(), anyBoolean());
verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+ verify(l, times(0)).updateAllAlarms();
+ verify(l, times(0)).updateAlarmsForUid(anyInt());
verify(l, times(0)).unblockAllUnrestrictedAlarms();
verify(l, times(0)).unblockAlarmsForUid(anyInt());
verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
@@ -1037,9 +1251,8 @@
// -------------------------------------------------------------------------
// Tests with proc state changes.
- // With battery save.
- mPowerSaveMode = true;
- mPowerSaveObserver.accept(getPowerSaveState());
+ // With battery saver.
+ // Battery saver is already on.
mIUidObserver.onUidActive(UID_10_1);
@@ -1049,6 +1262,8 @@
verify(l, times(1)).updateJobsForUid(eq(UID_10_1), anyBoolean());
verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+ verify(l, times(0)).updateAllAlarms();
+ verify(l, times(1)).updateAlarmsForUid(eq(UID_10_1));
verify(l, times(0)).unblockAllUnrestrictedAlarms();
verify(l, times(1)).unblockAlarmsForUid(eq(UID_10_1));
verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
@@ -1062,6 +1277,8 @@
verify(l, times(1)).updateJobsForUid(eq(UID_10_1), anyBoolean());
verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+ verify(l, times(0)).updateAllAlarms();
+ verify(l, times(1)).updateAlarmsForUid(eq(UID_10_1));
verify(l, times(0)).unblockAllUnrestrictedAlarms();
verify(l, times(0)).unblockAlarmsForUid(anyInt());
verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
@@ -1075,6 +1292,8 @@
verify(l, times(1)).updateJobsForUid(eq(UID_10_1), anyBoolean());
verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+ verify(l, times(0)).updateAllAlarms();
+ verify(l, times(1)).updateAlarmsForUid(eq(UID_10_1));
verify(l, times(0)).unblockAllUnrestrictedAlarms();
verify(l, times(1)).unblockAlarmsForUid(eq(UID_10_1));
verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
@@ -1088,12 +1307,14 @@
verify(l, times(1)).updateJobsForUid(eq(UID_10_1), anyBoolean());
verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+ verify(l, times(0)).updateAllAlarms();
+ verify(l, times(1)).updateAlarmsForUid(eq(UID_10_1));
verify(l, times(0)).unblockAllUnrestrictedAlarms();
verify(l, times(0)).unblockAlarmsForUid(anyInt());
verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
reset(l);
- // Without battery save.
+ // Without battery saver.
mPowerSaveMode = false;
mPowerSaveObserver.accept(getPowerSaveState());
@@ -1102,7 +1323,9 @@
verify(l, times(0)).updateJobsForUid(eq(UID_10_1), anyBoolean());
verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
- verify(l, times(1)).unblockAllUnrestrictedAlarms();
+ verify(l, times(1)).updateAllAlarms();
+ verify(l, times(0)).updateAlarmsForUid(eq(UID_10_1));
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
verify(l, times(0)).unblockAlarmsForUid(anyInt());
verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
reset(l);
@@ -1115,6 +1338,8 @@
verify(l, times(1)).updateJobsForUid(eq(UID_10_1), anyBoolean());
verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+ verify(l, times(0)).updateAllAlarms();
+ verify(l, times(1)).updateAlarmsForUid(eq(UID_10_1));
verify(l, times(0)).unblockAllUnrestrictedAlarms();
verify(l, times(1)).unblockAlarmsForUid(eq(UID_10_1));
verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
@@ -1128,6 +1353,8 @@
verify(l, times(1)).updateJobsForUid(eq(UID_10_1), anyBoolean());
verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+ verify(l, times(0)).updateAllAlarms();
+ verify(l, times(1)).updateAlarmsForUid(eq(UID_10_1));
verify(l, times(0)).unblockAllUnrestrictedAlarms();
verify(l, times(0)).unblockAlarmsForUid(anyInt());
verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
@@ -1141,6 +1368,8 @@
verify(l, times(1)).updateJobsForUid(eq(UID_10_1), anyBoolean());
verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+ verify(l, times(0)).updateAllAlarms();
+ verify(l, times(1)).updateAlarmsForUid(eq(UID_10_1));
verify(l, times(0)).unblockAllUnrestrictedAlarms();
verify(l, times(1)).unblockAlarmsForUid(eq(UID_10_1));
verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
@@ -1154,6 +1383,8 @@
verify(l, times(1)).updateJobsForUid(eq(UID_10_1), anyBoolean());
verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+ verify(l, times(0)).updateAllAlarms();
+ verify(l, times(1)).updateAlarmsForUid(eq(UID_10_1));
verify(l, times(0)).unblockAllUnrestrictedAlarms();
verify(l, times(0)).unblockAlarmsForUid(anyInt());
verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
diff --git a/services/tests/mockingservicestests/src/com/android/server/OWNERS b/services/tests/mockingservicestests/src/com/android/server/OWNERS
index e779e21..c0f0ce0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/OWNERS
+++ b/services/tests/mockingservicestests/src/com/android/server/OWNERS
@@ -1 +1,3 @@
per-file *Alarm* = file:/apex/jobscheduler/OWNERS
+per-file *AppStateTracker* = file:/apex/jobscheduler/OWNERS
+per-file *DeviceIdleController* = file:/apex/jobscheduler/OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index f375421..fd364ae7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -233,8 +233,10 @@
verifiedTimesMap);
noteBoot(4);
+ assertTrue(RescueParty.isRebootPropertySet());
- assertTrue(RescueParty.isAttemptingFactoryReset());
+ noteBoot(5);
+ assertTrue(RescueParty.isFactoryResetPropertySet());
}
@Test
@@ -255,7 +257,10 @@
/*configResetVerifiedTimesMap=*/ null);
notePersistentAppCrash(4);
- assertTrue(RescueParty.isAttemptingFactoryReset());
+ assertTrue(RescueParty.isRebootPropertySet());
+
+ notePersistentAppCrash(5);
+ assertTrue(RescueParty.isFactoryResetPropertySet());
}
@Test
@@ -306,7 +311,11 @@
observer.execute(new VersionedPackage(
CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4);
- assertTrue(RescueParty.isAttemptingFactoryReset());
+ assertTrue(RescueParty.isRebootPropertySet());
+
+ observer.execute(new VersionedPackage(
+ CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 5);
+ assertTrue(RescueParty.isFactoryResetPropertySet());
}
@Test
@@ -367,7 +376,11 @@
observer.execute(new VersionedPackage(
CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4);
- assertTrue(RescueParty.isAttemptingFactoryReset());
+ assertTrue(RescueParty.isRebootPropertySet());
+
+ observer.execute(new VersionedPackage(
+ CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 5);
+ assertTrue(RescueParty.isFactoryResetPropertySet());
}
@Test
@@ -376,6 +389,7 @@
noteBoot(i + 1);
}
assertTrue(RescueParty.isAttemptingFactoryReset());
+ assertTrue(RescueParty.isFactoryResetPropertySet());
}
@Test
@@ -424,7 +438,7 @@
for (int i = 0; i < LEVEL_FACTORY_RESET; i++) {
noteBoot(i + 1);
}
- assertFalse(RescueParty.isAttemptingFactoryReset());
+ assertFalse(RescueParty.isFactoryResetPropertySet());
// Restore the property value initialized in SetUp()
SystemProperties.set(PROP_DISABLE_FACTORY_RESET_FLAG, "");
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 8edac4f..7a970a1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -52,6 +52,7 @@
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MAX_INTERVAL;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_FUTURITY;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_INTERVAL;
+import static com.android.server.alarm.AlarmManagerService.INDEFINITE_DELAY;
import static com.android.server.alarm.AlarmManagerService.IS_WAKEUP_MASK;
import static com.android.server.alarm.AlarmManagerService.TIME_CHANGED_MASK;
import static com.android.server.alarm.AlarmManagerService.WORKING_INDEX;
@@ -71,6 +72,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.never;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
@@ -817,14 +819,14 @@
}
@Test
- public void testAlarmRestrictedInBatterySaver() throws Exception {
+ public void testAlarmRestrictedByFAS() throws Exception {
final ArgumentCaptor<AppStateTrackerImpl.Listener> listenerArgumentCaptor =
ArgumentCaptor.forClass(AppStateTrackerImpl.Listener.class);
verify(mAppStateTracker).addListener(listenerArgumentCaptor.capture());
final PendingIntent alarmPi = getNewMockPendingIntent();
- when(mAppStateTracker.areAlarmsRestricted(TEST_CALLING_UID, TEST_CALLING_PACKAGE,
- false)).thenReturn(true);
+ when(mAppStateTracker.areAlarmsRestricted(TEST_CALLING_UID,
+ TEST_CALLING_PACKAGE)).thenReturn(true);
setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 2, alarmPi);
assertEquals(mNowElapsedTest + 2, mTestTimer.getElapsed());
@@ -1301,7 +1303,6 @@
final long awiDelayForTest = 23;
setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_LONG_TIME, awiDelayForTest);
- setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_SHORT_TIME, 0);
setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 1000,
getNewMockPendingIntent());
@@ -1336,7 +1337,7 @@
}
@Test
- public void allowWhileIdleUnrestricted() throws Exception {
+ public void allowWhileIdleUnrestrictedInIdle() throws Exception {
doReturn(0).when(mService).fuzzForDuration(anyLong());
final long awiDelayForTest = 127;
@@ -1361,7 +1362,7 @@
}
@Test
- public void deviceIdleThrottling() throws Exception {
+ public void deviceIdleDeferralOnSet() throws Exception {
doReturn(0).when(mService).fuzzForDuration(anyLong());
final long deviceIdleUntil = mNowElapsedTest + 1234;
@@ -1386,6 +1387,123 @@
}
@Test
+ public void deviceIdleStateChanges() throws Exception {
+ doReturn(0).when(mService).fuzzForDuration(anyLong());
+
+ final int numAlarms = 10;
+ final PendingIntent[] pis = new PendingIntent[numAlarms];
+ for (int i = 0; i < numAlarms; i++) {
+ setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + i + 1,
+ pis[i] = getNewMockPendingIntent());
+ assertEquals(mNowElapsedTest + 1, mTestTimer.getElapsed());
+ }
+
+ final PendingIntent idleUntil = getNewMockPendingIntent();
+ setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 1234, idleUntil);
+
+ assertEquals(mNowElapsedTest + 1234, mTestTimer.getElapsed());
+
+ mNowElapsedTest += 5;
+ mTestTimer.expire();
+ // Nothing should happen.
+ verify(pis[0], never()).send(eq(mMockContext), eq(0), any(Intent.class), any(),
+ any(Handler.class), isNull(), any());
+
+ mService.removeLocked(idleUntil, null);
+ mTestTimer.expire();
+ // Now, the first 5 alarms (upto i = 4) should expire.
+ for (int i = 0; i < 5; i++) {
+ verify(pis[i]).send(eq(mMockContext), eq(0), any(Intent.class), any(),
+ any(Handler.class), isNull(), any());
+ }
+ // Rest should be restored, so the timer should reflect the next alarm.
+ assertEquals(mNowElapsedTest + 1, mTestTimer.getElapsed());
+ }
+
+ @Test
+ public void batterySaverThrottling() {
+ final ArgumentCaptor<AppStateTrackerImpl.Listener> listenerArgumentCaptor =
+ ArgumentCaptor.forClass(AppStateTrackerImpl.Listener.class);
+ verify(mAppStateTracker).addListener(listenerArgumentCaptor.capture());
+ final AppStateTrackerImpl.Listener listener = listenerArgumentCaptor.getValue();
+
+ final PendingIntent alarmPi = getNewMockPendingIntent();
+ when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID,
+ TEST_CALLING_PACKAGE)).thenReturn(true);
+ setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 7, alarmPi);
+ assertEquals(mNowElapsedTest + INDEFINITE_DELAY, mTestTimer.getElapsed());
+
+ when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID,
+ TEST_CALLING_PACKAGE)).thenReturn(false);
+ listener.updateAllAlarms();
+ assertEquals(mNowElapsedTest + 7, mTestTimer.getElapsed());
+
+ when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID,
+ TEST_CALLING_PACKAGE)).thenReturn(true);
+ listener.updateAlarmsForUid(TEST_CALLING_UID);
+ assertEquals(mNowElapsedTest + INDEFINITE_DELAY, mTestTimer.getElapsed());
+ }
+
+ @Test
+ public void allowWhileIdleAlarmsInBatterySaver() throws Exception {
+ final ArgumentCaptor<AppStateTrackerImpl.Listener> listenerArgumentCaptor =
+ ArgumentCaptor.forClass(AppStateTrackerImpl.Listener.class);
+ verify(mAppStateTracker).addListener(listenerArgumentCaptor.capture());
+ final AppStateTrackerImpl.Listener listener = listenerArgumentCaptor.getValue();
+
+ final long longDelay = 23;
+ final long shortDelay = 7;
+ setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_LONG_TIME, longDelay);
+ setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_SHORT_TIME, shortDelay);
+
+ when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID,
+ TEST_CALLING_PACKAGE)).thenReturn(true);
+ setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 1,
+ getNewMockPendingIntent(), false);
+ setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 2,
+ getNewMockPendingIntent(), false);
+
+ assertEquals(mNowElapsedTest + 1, mTestTimer.getElapsed());
+
+ mNowElapsedTest += 1;
+ mTestTimer.expire();
+
+ assertEquals(mNowElapsedTest + longDelay, mTestTimer.getElapsed());
+ listener.onUidForeground(TEST_CALLING_UID, true);
+ // The next alarm should be deferred by shortDelay.
+ assertEquals(mNowElapsedTest + shortDelay, mTestTimer.getElapsed());
+
+ mNowElapsedTest = mTestTimer.getElapsed();
+ setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 1,
+ getNewMockPendingIntent(), false);
+
+ when(mAppStateTracker.isUidInForeground(TEST_CALLING_UID)).thenReturn(true);
+ mTestTimer.expire();
+ // The next alarm should be deferred by shortDelay again.
+ assertEquals(mNowElapsedTest + shortDelay, mTestTimer.getElapsed());
+
+ mNowElapsedTest = mTestTimer.getElapsed();
+ setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 1,
+ getNewMockPendingIntent(), true);
+ when(mAppStateTracker.isUidInForeground(TEST_CALLING_UID)).thenReturn(false);
+ mTestTimer.expire();
+ final long lastAwiDispatch = mNowElapsedTest;
+ // Unrestricted, so should not be changed.
+ assertEquals(mNowElapsedTest + 1, mTestTimer.getElapsed());
+
+ mNowElapsedTest = mTestTimer.getElapsed();
+ // AWI_unrestricted should not affect normal AWI bookkeeping.
+ // The next alarm is after the short delay but before the long delay.
+ setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, lastAwiDispatch + shortDelay + 1,
+ getNewMockPendingIntent(), false);
+ mTestTimer.expire();
+ assertEquals(lastAwiDispatch + longDelay, mTestTimer.getElapsed());
+
+ listener.onUidForeground(TEST_CALLING_UID, true);
+ assertEquals(lastAwiDispatch + shortDelay + 1, mTestTimer.getElapsed());
+ }
+
+ @Test
public void dispatchOrder() throws Exception {
doReturn(0).when(mService).fuzzForDuration(anyLong());
diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/OWNERS b/services/tests/servicestests/src/com/android/server/apphibernation/OWNERS
new file mode 100644
index 0000000..c2e27e0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/apphibernation/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/apphibernation/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
index f29b059..c6fde87 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
@@ -22,10 +22,12 @@
import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.GenericDocument;
+import android.app.appsearch.SearchResult;
import android.app.appsearch.SearchResultPage;
import android.app.appsearch.SearchSpec;
import android.app.appsearch.exceptions.AppSearchException;
+import com.android.server.appsearch.external.localstorage.converter.GenericDocumentToProtoConverter;
import com.android.server.appsearch.external.localstorage.converter.SchemaToProtoConverter;
import com.android.server.appsearch.proto.DocumentProto;
import com.android.server.appsearch.proto.GetOptimizeInfoResultProto;
@@ -33,6 +35,7 @@
import com.android.server.appsearch.proto.PropertyProto;
import com.android.server.appsearch.proto.SchemaProto;
import com.android.server.appsearch.proto.SchemaTypeConfigProto;
+import com.android.server.appsearch.proto.SearchResultProto;
import com.android.server.appsearch.proto.SearchSpecProto;
import com.android.server.appsearch.proto.StringIndexingConfig;
import com.android.server.appsearch.proto.TermMatchType;
@@ -316,14 +319,14 @@
DocumentProto insideDocument =
DocumentProto.newBuilder()
.setUri("inside-uri")
- .setSchema("package$databaseName1/type")
- .setNamespace("package$databaseName2/namespace")
+ .setSchema("package$databaseName/type")
+ .setNamespace("package$databaseName/namespace")
.build();
DocumentProto documentProto =
DocumentProto.newBuilder()
.setUri("uri")
- .setSchema("package$databaseName2/type")
- .setNamespace("package$databaseName3/namespace")
+ .setSchema("package$databaseName/type")
+ .setNamespace("package$databaseName/namespace")
.addProperties(PropertyProto.newBuilder().addDocumentValues(insideDocument))
.build();
@@ -345,11 +348,56 @@
.build();
DocumentProto.Builder actualDocument = documentProto.toBuilder();
- mAppSearchImpl.removePrefixesFromDocument(actualDocument);
+ assertThat(mAppSearchImpl.removePrefixesFromDocument(actualDocument))
+ .isEqualTo("package$databaseName/");
assertThat(actualDocument.build()).isEqualTo(expectedDocumentProto);
}
@Test
+ public void testRemoveDatabasesFromDocumentThrowsException() throws Exception {
+ // Set two different database names in the document, which should never happen
+ DocumentProto documentProto =
+ DocumentProto.newBuilder()
+ .setUri("uri")
+ .setSchema("prefix1/type")
+ .setNamespace("prefix2/namespace")
+ .build();
+
+ DocumentProto.Builder actualDocument = documentProto.toBuilder();
+ AppSearchException e =
+ expectThrows(
+ AppSearchException.class,
+ () -> mAppSearchImpl.removePrefixesFromDocument(actualDocument));
+ assertThat(e).hasMessageThat().contains("Found unexpected multiple prefix names");
+ }
+
+ @Test
+ public void testNestedRemoveDatabasesFromDocumentThrowsException() throws Exception {
+ // Set two different database names in the outer and inner document, which should never
+ // happen.
+ DocumentProto insideDocument =
+ DocumentProto.newBuilder()
+ .setUri("inside-uri")
+ .setSchema("prefix1/type")
+ .setNamespace("prefix1/namespace")
+ .build();
+ DocumentProto documentProto =
+ DocumentProto.newBuilder()
+ .setUri("uri")
+ .setSchema("prefix2/type")
+ .setNamespace("prefix2/namespace")
+ .addProperties(PropertyProto.newBuilder().addDocumentValues(insideDocument))
+ .build();
+
+ DocumentProto.Builder actualDocument = documentProto.toBuilder();
+ AppSearchException e =
+ expectThrows(
+ AppSearchException.class,
+ () -> mAppSearchImpl.removePrefixesFromDocument(actualDocument));
+ assertThat(e).hasMessageThat().contains("Found unexpected multiple prefix names");
+ }
+
+ @Test
public void testOptimize() throws Exception {
// Insert schema
List<AppSearchSchema> schemas =
@@ -865,4 +913,40 @@
AppSearchImpl.createPrefix("package", "database1"),
AppSearchImpl.createPrefix("package", "database2"));
}
+
+ @Test
+ public void testRewriteSearchResultProto() throws Exception {
+ final String database =
+ "com.package.foo"
+ + AppSearchImpl.PACKAGE_DELIMITER
+ + "databaseName"
+ + AppSearchImpl.DATABASE_DELIMITER;
+ final String uri = "uri";
+ final String namespace = database + "namespace";
+ final String schemaType = database + "schema";
+
+ // Building the SearchResult received from query.
+ DocumentProto documentProto =
+ DocumentProto.newBuilder()
+ .setUri(uri)
+ .setNamespace(namespace)
+ .setSchema(schemaType)
+ .build();
+ SearchResultProto.ResultProto resultProto =
+ SearchResultProto.ResultProto.newBuilder().setDocument(documentProto).build();
+ SearchResultProto searchResultProto =
+ SearchResultProto.newBuilder().addResults(resultProto).build();
+
+ DocumentProto.Builder strippedDocumentProto = documentProto.toBuilder();
+ AppSearchImpl.removePrefixesFromDocument(strippedDocumentProto);
+ SearchResultPage searchResultPage =
+ AppSearchImpl.rewriteSearchResultProto(searchResultProto);
+ for (SearchResult result : searchResultPage.getResults()) {
+ assertThat(result.getPackageName()).isEqualTo("com.package.foo");
+ assertThat(result.getDocument())
+ .isEqualTo(
+ GenericDocumentToProtoConverter.toGenericDocument(
+ strippedDocumentProto.build()));
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SnippetTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SnippetTest.java
index 7c68c6b..a3f0f6b 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SnippetTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SnippetTest.java
@@ -29,6 +29,8 @@
import org.junit.Test;
+import java.util.Collections;
+
public class SnippetTest {
// TODO(tytytyww): Add tests for Double and Long Snippets.
@@ -83,7 +85,8 @@
// Making ResultReader and getting Snippet values.
SearchResultPage searchResultPage =
- SearchResultToProtoConverter.toSearchResultPage(searchResultProto);
+ SearchResultToProtoConverter.toSearchResultPage(
+ searchResultProto, Collections.singletonList("packageName"));
for (SearchResult result : searchResultPage.getResults()) {
SearchResult.MatchInfo match = result.getMatches().get(0);
assertThat(match.getPropertyPath()).isEqualTo(propertyKeyString);
@@ -131,7 +134,8 @@
SearchResultProto.newBuilder().addResults(resultProto).build();
SearchResultPage searchResultPage =
- SearchResultToProtoConverter.toSearchResultPage(searchResultProto);
+ SearchResultToProtoConverter.toSearchResultPage(
+ searchResultProto, Collections.singletonList("packageName"));
for (SearchResult result : searchResultPage.getResults()) {
assertThat(result.getMatches()).isEmpty();
}
@@ -196,7 +200,8 @@
// Making ResultReader and getting Snippet values.
SearchResultPage searchResultPage =
- SearchResultToProtoConverter.toSearchResultPage(searchResultProto);
+ SearchResultToProtoConverter.toSearchResultPage(
+ searchResultProto, Collections.singletonList("packageName"));
for (SearchResult result : searchResultPage.getResults()) {
SearchResult.MatchInfo match1 = result.getMatches().get(0);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/InvalidationTrackerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/InvalidationTrackerTest.java
new file mode 100644
index 0000000..340a1d9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/InvalidationTrackerTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricManager.Authenticators;
+import android.hardware.biometrics.IBiometricAuthenticator;
+import android.hardware.biometrics.IInvalidationCallback;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.biometrics.BiometricService.InvalidationTracker;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+
+@Presubmit
+@SmallTest
+public class InvalidationTrackerTest {
+
+ @Test
+ public void testCallbackReceived_whenAllStrongSensorsInvalidated() throws Exception {
+ final IBiometricAuthenticator authenticator1 = mock(IBiometricAuthenticator.class);
+ final TestSensor sensor1 = new TestSensor(0 /* id */,
+ BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+ authenticator1);
+
+ final IBiometricAuthenticator authenticator2 = mock(IBiometricAuthenticator.class);
+ final TestSensor sensor2 = new TestSensor(1 /* id */,
+ BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+ authenticator2);
+
+ final IBiometricAuthenticator authenticator3 = mock(IBiometricAuthenticator.class);
+ final TestSensor sensor3 = new TestSensor(2 /* id */,
+ BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG,
+ authenticator3);
+
+ final IBiometricAuthenticator authenticator4 = mock(IBiometricAuthenticator.class);
+ final TestSensor sensor4 = new TestSensor(3 /* id */,
+ BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_WEAK,
+ authenticator4);
+
+ final ArrayList<BiometricSensor> sensors = new ArrayList<>();
+ sensors.add(sensor1);
+ sensors.add(sensor2);
+ sensors.add(sensor3);
+ sensors.add(sensor4);
+
+ final IInvalidationCallback callback = mock(IInvalidationCallback.class);
+ final InvalidationTracker tracker =
+ InvalidationTracker.start(sensors, 0 /* userId */, 0 /* fromSensorId */, callback);
+
+ // The sensor which the request originated from should not be requested to invalidate
+ // its authenticatorId.
+ verify(authenticator1, never()).invalidateAuthenticatorId(anyInt(), any());
+
+ // All other strong sensors should be requested to invalidate authenticatorId
+ verify(authenticator2).invalidateAuthenticatorId(eq(0) /* userId */, any());
+ verify(authenticator3).invalidateAuthenticatorId(eq(0) /* userId */, any());
+
+ // Weak sensors are not requested to invalidate authenticatorId
+ verify(authenticator4, never()).invalidateAuthenticatorId(anyInt(), any());
+
+ // Client is not notified until invalidation for all required sensors have completed
+ verify(callback, never()).onCompleted();
+ tracker.onInvalidated(1);
+ verify(callback, never()).onCompleted();
+ tracker.onInvalidated(2);
+ verify(callback).onCompleted();
+ }
+
+ private static class TestSensor extends BiometricSensor {
+
+ TestSensor(int id, int modality, int strength, IBiometricAuthenticator impl) {
+ super(id, modality, strength, impl);
+ }
+
+ @Override
+ boolean confirmationAlwaysRequired(int userId) {
+ return false;
+ }
+
+ @Override
+ boolean confirmationSupported() {
+ return false;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java
index 4f4aa3f..f00edcc 100644
--- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java
@@ -40,78 +40,83 @@
}
CompatConfigBuilder addEnableAfterSdkChangeWithId(int sdk, long id) {
- mChanges.add(new CompatChange(id, "", sdk, -1, false, false, ""));
+ mChanges.add(new CompatChange(id, "", sdk, -1, false, false, "", false));
return this;
}
CompatConfigBuilder addEnableAfterSdkChangeWithIdAndName(int sdk, long id, String name) {
- mChanges.add(new CompatChange(id, name, sdk, -1, false, false, ""));
+ mChanges.add(new CompatChange(id, name, sdk, -1, false, false, "", false));
return this;
}
CompatConfigBuilder addEnableAfterSdkChangeWithIdDefaultDisabled(int sdk, long id) {
- mChanges.add(new CompatChange(id, "", sdk, -1, true, false, ""));
+ mChanges.add(new CompatChange(id, "", sdk, -1, true, false, "", false));
return this;
}
CompatConfigBuilder addEnableAfterSdkChangeWithIdAndDescription(int sdk, long id,
String description) {
- mChanges.add(new CompatChange(id, "", sdk, -1, false, false, description));
+ mChanges.add(new CompatChange(id, "", sdk, -1, false, false, description, false));
return this;
}
CompatConfigBuilder addEnableSinceSdkChangeWithId(int sdk, long id) {
- mChanges.add(new CompatChange(id, "", -1, sdk, false, false, ""));
+ mChanges.add(new CompatChange(id, "", -1, sdk, false, false, "", false));
return this;
}
CompatConfigBuilder addEnableSinceSdkChangeWithIdAndName(int sdk, long id, String name) {
- mChanges.add(new CompatChange(id, name, -1, sdk, false, false, ""));
+ mChanges.add(new CompatChange(id, name, -1, sdk, false, false, "", false));
return this;
}
CompatConfigBuilder addEnableSinceSdkChangeWithIdDefaultDisabled(int sdk, long id) {
- mChanges.add(new CompatChange(id, "", -1, sdk, true, false, ""));
+ mChanges.add(new CompatChange(id, "", -1, sdk, true, false, "", false));
return this;
}
CompatConfigBuilder addEnableSinceSdkChangeWithIdAndDescription(int sdk, long id,
String description) {
- mChanges.add(new CompatChange(id, "", -1, sdk, false, false, description));
+ mChanges.add(new CompatChange(id, "", -1, sdk, false, false, description, false));
return this;
}
CompatConfigBuilder addEnabledChangeWithId(long id) {
- mChanges.add(new CompatChange(id, "", -1, -1, false, false, ""));
+ mChanges.add(new CompatChange(id, "", -1, -1, false, false, "", false));
return this;
}
CompatConfigBuilder addEnabledChangeWithIdAndName(long id, String name) {
- mChanges.add(new CompatChange(id, name, -1, -1, false, false, ""));
+ mChanges.add(new CompatChange(id, name, -1, -1, false, false, "", false));
return this;
}
CompatConfigBuilder addEnabledChangeWithIdAndDescription(long id, String description) {
- mChanges.add(new CompatChange(id, "", -1, -1, false, false, description));
+ mChanges.add(new CompatChange(id, "", -1, -1, false, false, description, false));
return this;
}
CompatConfigBuilder addDisabledChangeWithId(long id) {
- mChanges.add(new CompatChange(id, "", -1, -1, true, false, ""));
+ mChanges.add(new CompatChange(id, "", -1, -1, true, false, "", false));
return this;
}
CompatConfigBuilder addDisabledChangeWithIdAndName(long id, String name) {
- mChanges.add(new CompatChange(id, name, -1, -1, true, false, ""));
+ mChanges.add(new CompatChange(id, name, -1, -1, true, false, "", false));
return this;
}
CompatConfigBuilder addDisabledChangeWithIdAndDescription(long id, String description) {
- mChanges.add(new CompatChange(id, "", -1, -1, true, false, description));
+ mChanges.add(new CompatChange(id, "", -1, -1, true, false, description, false));
return this;
}
CompatConfigBuilder addLoggingOnlyChangeWithId(long id) {
- mChanges.add(new CompatChange(id, "", -1, -1, false, true, ""));
+ mChanges.add(new CompatChange(id, "", -1, -1, false, true, "", false));
+ return this;
+ }
+
+ CompatConfigBuilder addOverridableChangeWithId(long id) {
+ mChanges.add(new CompatChange(id, "", -1, -1, false, true, "", true));
return this;
}
diff --git a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
index a70c510..a1b2dc8 100644
--- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
@@ -97,17 +97,22 @@
.addEnableAfterSdkChangeWithId(Build.VERSION_CODES.Q, 5L)
.addEnableAfterSdkChangeWithId(Build.VERSION_CODES.R, 6L)
.addLoggingOnlyChangeWithId(7L)
+ .addOverridableChangeWithId(8L)
.build();
mPlatformCompat = new PlatformCompat(mContext, mCompatConfig);
assertThat(mPlatformCompat.listAllChanges()).asList().containsExactly(
- new CompatibilityChangeInfo(1L, "", -1, -1, false, false, ""),
- new CompatibilityChangeInfo(2L, "change2", -1, -1, true, false, ""),
+ new CompatibilityChangeInfo(1L, "", -1, -1, false, false, "", false),
+ new CompatibilityChangeInfo(2L, "change2", -1, -1, true, false, "", false),
new CompatibilityChangeInfo(3L, "", Build.VERSION_CODES.O, -1, false, false,
- "desc"),
- new CompatibilityChangeInfo(4L, "", Build.VERSION_CODES.P, -1, false, false, ""),
- new CompatibilityChangeInfo(5L, "", Build.VERSION_CODES.Q, -1, false, false, ""),
- new CompatibilityChangeInfo(6L, "", Build.VERSION_CODES.R, -1, false, false, ""),
- new CompatibilityChangeInfo(7L, "", -1, -1, false, true, ""));
+ "desc", false),
+ new CompatibilityChangeInfo(
+ 4L, "", Build.VERSION_CODES.P, -1, false, false, "", false),
+ new CompatibilityChangeInfo(
+ 5L, "", Build.VERSION_CODES.Q, -1, false, false, "", false),
+ new CompatibilityChangeInfo(
+ 6L, "", Build.VERSION_CODES.R, -1, false, false, "", false),
+ new CompatibilityChangeInfo(7L, "", -1, -1, false, true, "", false),
+ new CompatibilityChangeInfo(8L, "", -1, -1, false, true, "", true));
}
@Test
@@ -123,12 +128,12 @@
.build();
mPlatformCompat = new PlatformCompat(mContext, mCompatConfig);
assertThat(mPlatformCompat.listUIChanges()).asList().containsExactly(
- new CompatibilityChangeInfo(1L, "", -1, -1, false, false, ""),
- new CompatibilityChangeInfo(2L, "change2", -1, -1, true, false, ""),
- new CompatibilityChangeInfo(5L, "", /*enableAfter*/ -1,
- /*enableSince*/ Build.VERSION_CODES.Q, false, false, ""),
- new CompatibilityChangeInfo(6L, "", /*enableAfter*/ -1,
- /*enableSince*/ Build.VERSION_CODES.R, false, false, ""));
+ new CompatibilityChangeInfo(1L, "", -1, -1, false, false, "", false),
+ new CompatibilityChangeInfo(2L, "change2", -1, -1, true, false, "", false),
+ new CompatibilityChangeInfo(
+ 5L, "", Build.VERSION_CODES.P, -1, false, false, "", false),
+ new CompatibilityChangeInfo(
+ 6L, "", Build.VERSION_CODES.Q, -1, false, false, "", false));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 17324ba..9c28c99 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -460,6 +460,11 @@
}
@Override
+ KeyChain.KeyChainConnection keyChainBind() {
+ return services.keyChainConnection;
+ }
+
+ @Override
KeyChain.KeyChainConnection keyChainBindAsUser(UserHandle user) {
return services.keyChainConnection;
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index c1b1133..a455ba9 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -1628,6 +1628,33 @@
)), eq(user));
}
+ @Test
+ public void testRemoveCredentialManagementApp() throws Exception {
+ final String packageName = "com.test.cred.mng";
+ Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
+ intent.setData(Uri.parse("package:" + packageName));
+ dpms.mReceiver.setPendingResult(
+ new BroadcastReceiver.PendingResult(Activity.RESULT_OK,
+ "resultData",
+ /* resultExtras= */ null,
+ BroadcastReceiver.PendingResult.TYPE_UNREGISTERED,
+ /* ordered= */ true,
+ /* sticky= */ false,
+ /* token= */ null,
+ CALLER_USER_HANDLE,
+ /* flags= */ 0));
+ when(getServices().keyChainConnection.getService().hasCredentialManagementApp())
+ .thenReturn(true);
+ when(getServices().keyChainConnection.getService().getCredentialManagementAppPackageName())
+ .thenReturn(packageName);
+
+ dpms.mReceiver.onReceive(mContext, intent);
+
+ flushTasks(dpms);
+ verify(getServices().keyChainConnection.getService()).hasCredentialManagementApp();
+ verify(getServices().keyChainConnection.getService()).removeCredentialManagementApp();
+ }
+
/**
* Simple test for delegate set/get and general delegation. Tests verifying that delegated
* privileges can acually be exercised by a delegate are not covered here.
@@ -6891,6 +6918,35 @@
DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED);
}
+ @Test
+ public void testSetRequiredPasswordComplexityFailsWithQualityOnParent() throws Exception {
+ final int managedProfileUserId = CALLER_USER_HANDLE;
+ final int managedProfileAdminUid =
+ UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID);
+ mContext.binder.callingUid = managedProfileAdminUid;
+ addManagedProfile(admin1, managedProfileAdminUid, admin1, VERSION_CODES.R);
+
+ parentDpm.setPasswordQuality(admin1, DevicePolicyManager.PASSWORD_QUALITY_COMPLEX);
+
+ assertThrows(IllegalStateException.class,
+ () -> dpm.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_HIGH));
+ }
+
+ @Test
+ public void testSetQualityOnParentFailsWithComplexityOnProfile() throws Exception {
+ final int managedProfileUserId = CALLER_USER_HANDLE;
+ final int managedProfileAdminUid =
+ UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID);
+ mContext.binder.callingUid = managedProfileAdminUid;
+ addManagedProfile(admin1, managedProfileAdminUid, admin1, VERSION_CODES.R);
+
+ dpm.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_HIGH);
+
+ assertThrows(IllegalStateException.class,
+ () -> parentDpm.setPasswordQuality(admin1,
+ DevicePolicyManager.PASSWORD_QUALITY_COMPLEX));
+ }
+
private void setUserUnlocked(int userHandle, boolean unlocked) {
when(getServices().userManager.isUserUnlocked(eq(userHandle))).thenReturn(unlocked);
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/EnterpriseSpecificIdCalculatorTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/EnterpriseSpecificIdCalculatorTest.java
new file mode 100644
index 0000000..c2c1d5b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/EnterpriseSpecificIdCalculatorTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.devicepolicy;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class EnterpriseSpecificIdCalculatorTest {
+ private static final String SOME_IMEI = "56134231542345";
+ private static final String SOME_SERIAL_NUMBER = "XZ663CCAJA7";
+ private static final String SOME_MAC_ADDRESS = "65:ca:f3:fe:9d:b1";
+ private static final String NO_MEID = null;
+ private static final String SOME_PACKAGE = "com.example.test.dpc";
+ private static final String ANOTHER_PACKAGE = "org.example.test.another.dpc";
+ private static final String SOME_ENTERPRISE_ID = "73456234";
+ private static final String ANOTHER_ENTERPRISE_ID = "243441";
+
+ private EnterpriseSpecificIdCalculator mEsidCalculator;
+
+ @Before
+ public void createDefaultEsidCalculator() {
+ mEsidCalculator = new EnterpriseSpecificIdCalculator(SOME_IMEI, NO_MEID, SOME_SERIAL_NUMBER,
+ SOME_MAC_ADDRESS);
+ }
+
+ @Test
+ public void paddingOfIdentifiers() {
+ assertThat(mEsidCalculator.getPaddedImei()).isEqualTo(" 56134231542345");
+ assertThat(mEsidCalculator.getPaddedMeid()).isEqualTo(" ");
+ assertThat(mEsidCalculator.getPaddedSerialNumber()).isEqualTo(" XZ663CCAJA7");
+ }
+
+ @Test
+ public void truncationOfLongIdentifier() {
+ EnterpriseSpecificIdCalculator esidCalculator = new EnterpriseSpecificIdCalculator(
+ SOME_IMEI, NO_MEID, "XZ663CCAJA7XZ663CCAJA7XZ663CCAJA7",
+ SOME_MAC_ADDRESS);
+ assertThat(esidCalculator.getPaddedSerialNumber()).isEqualTo("XZ663CCAJA7XZ663");
+ }
+
+ @Test
+ public void paddingOfPackageName() {
+ assertThat(mEsidCalculator.getPaddedProfileOwnerName(SOME_PACKAGE)).isEqualTo(
+ " " + SOME_PACKAGE);
+ }
+
+ @Test
+ public void paddingOfEnterpriseId() {
+ assertThat(mEsidCalculator.getPaddedEnterpriseId(SOME_ENTERPRISE_ID)).isEqualTo(
+ " " + SOME_ENTERPRISE_ID);
+ }
+
+ @Test
+ public void emptyEnterpriseIdYieldsEmptyEsid() {
+ assertThrows(IllegalArgumentException.class, () ->
+ mEsidCalculator.calculateEnterpriseId(SOME_PACKAGE, ""));
+ }
+
+ @Test
+ public void emptyDpcPackageYieldsEmptyEsid() {
+ assertThrows(IllegalArgumentException.class, () ->
+ mEsidCalculator.calculateEnterpriseId("", SOME_ENTERPRISE_ID));
+ }
+
+ // On upgrade, an ESID will be calculated with an empty Enterprise ID. This is signalled
+ // to the EnterpriseSpecificIdCalculator by passing in null.
+ @Test
+ public void nullEnterpriseIdYieldsValidEsid() {
+ assertThat(mEsidCalculator.calculateEnterpriseId(SOME_PACKAGE, null)).isEqualTo(
+ "C4W7-VUJT-PHSA-HMY53-CLHX-L4HW-L");
+ }
+
+ @Test
+ public void knownValues() {
+ assertThat(
+ mEsidCalculator.calculateEnterpriseId(SOME_PACKAGE, SOME_ENTERPRISE_ID)).isEqualTo(
+ "FP7B-RXQW-Q77F-7J6FC-5RXZ-UJI6-6");
+ assertThat(mEsidCalculator.calculateEnterpriseId(SOME_PACKAGE,
+ ANOTHER_ENTERPRISE_ID)).isEqualTo("ATAL-VPIX-GBNZ-NE3TF-TDEV-3OVO-C");
+ assertThat(mEsidCalculator.calculateEnterpriseId(ANOTHER_PACKAGE,
+ SOME_ENTERPRISE_ID)).isEqualTo("JHU3-6SHH-YLHC-ZGETD-PWNI-7NPQ-S");
+ assertThat(mEsidCalculator.calculateEnterpriseId(ANOTHER_PACKAGE,
+ ANOTHER_ENTERPRISE_ID)).isEqualTo("LEF3-QBEC-UQ6O-RIOCX-TQF6-GRLV-F");
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 603608b..26c304f 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -34,6 +34,7 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static org.mockito.internal.verification.VerificationModeFactory.times;
import android.annotation.NonNull;
import android.content.ContentResolver;
@@ -505,7 +506,7 @@
createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
setPeakRefreshRate(90);
director.getSettingsObserver().setDefaultRefreshRate(90);
- director.getBrightnessObserver().setDefaultDisplayState(true);
+ director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
final FakeDeviceConfig config = mInjector.getDeviceConfig();
config.setRefreshRateInLowZone(90);
@@ -548,7 +549,7 @@
createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
setPeakRefreshRate(90 /*fps*/);
director.getSettingsObserver().setDefaultRefreshRate(90);
- director.getBrightnessObserver().setDefaultDisplayState(true);
+ director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
final FakeDeviceConfig config = mInjector.getDeviceConfig();
config.setRefreshRateInHighZone(60);
@@ -585,6 +586,43 @@
assertVoteForRefreshRateLocked(vote, 60 /*fps*/);
}
+ @Test
+ public void testSensorRegistration() {
+ DisplayModeDirector director =
+ createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+ setPeakRefreshRate(90 /*fps*/);
+ director.getSettingsObserver().setDefaultRefreshRate(90);
+ director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+
+ Sensor lightSensor = createLightSensor();
+ SensorManager sensorManager = createMockSensorManager(lightSensor);
+
+ director.start(sensorManager);
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ Mockito.verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
+ .registerListener(
+ listenerCaptor.capture(),
+ eq(lightSensor),
+ anyInt(),
+ any(Handler.class));
+
+ // Dispaly state changed from On to Doze
+ director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_DOZE);
+ Mockito.verify(sensorManager)
+ .unregisterListener(listenerCaptor.capture());
+
+ // Dispaly state changed from Doze to On
+ director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+ Mockito.verify(sensorManager, times(2))
+ .registerListener(
+ listenerCaptor.capture(),
+ eq(lightSensor),
+ anyInt(),
+ any(Handler.class));
+
+ }
+
private void assertVoteForRefreshRateLocked(Vote vote, float refreshRate) {
assertThat(vote).isNotNull();
final DisplayModeDirector.RefreshRateRange expectedRange =
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/OWNERS b/services/tests/servicestests/src/com/android/server/graphics/fonts/OWNERS
new file mode 100644
index 0000000..34ac813
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 24939
+
+include /graphics/java/android/graphics/fonts/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/net/watchlist/NetworkWatchlistServiceTests.java b/services/tests/servicestests/src/com/android/server/net/watchlist/NetworkWatchlistServiceTests.java
index 9c8a382..ac9316e 100644
--- a/services/tests/servicestests/src/com/android/server/net/watchlist/NetworkWatchlistServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/net/watchlist/NetworkWatchlistServiceTests.java
@@ -24,6 +24,9 @@
import android.net.ConnectivityMetricsEvent;
import android.net.IIpConnectivityMetrics;
import android.net.INetdEventCallback;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -106,6 +109,16 @@
counter--;
return true;
}
+
+ // TODO: mark @Override when aosp/1541935 automerges to master.
+ public void logDefaultNetworkValidity(boolean valid) {
+ }
+
+ // TODO: mark @Override when aosp/1541935 automerges to master.
+ public void logDefaultNetworkEvent(Network defaultNetwork, int score, boolean validated,
+ LinkProperties lp, NetworkCapabilities nc, Network previousDefaultNetwork,
+ int previousScore, LinkProperties previousLp, NetworkCapabilities previousNc) {
+ }
};
ServiceThread mHandlerThread;
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
index d54a40e..c010e19 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
@@ -29,6 +29,10 @@
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.util.HexDump;
+import com.android.server.pm.PerPackageReadTimeouts.Timeouts;
+import com.android.server.pm.PerPackageReadTimeouts.VersionCodes;
+
import com.google.android.collect.Lists;
import org.junit.After;
@@ -45,6 +49,7 @@
import java.util.List;
import java.util.regex.Pattern;
+// atest PackageManagerServiceTest
// runtest -c com.android.server.pm.PackageManagerServiceTest frameworks-services
// bit FrameworksServicesTests:com.android.server.pm.PackageManagerServiceTest
@RunWith(AndroidJUnit4.class)
@@ -182,6 +187,219 @@
}
}
+ @Test
+ public void testTimeouts() {
+ Timeouts defaults = Timeouts.parse("3600000001:3600000002:3600000003");
+ Assert.assertEquals(3600000001L, defaults.minTimeUs);
+ Assert.assertEquals(3600000002L, defaults.minPendingTimeUs);
+ Assert.assertEquals(3600000003L, defaults.maxPendingTimeUs);
+
+ Timeouts empty = Timeouts.parse("");
+ Assert.assertEquals(3600000000L, empty.minTimeUs);
+ Assert.assertEquals(3600000000L, empty.minPendingTimeUs);
+ Assert.assertEquals(3600000000L, empty.maxPendingTimeUs);
+
+ Timeouts partial0 = Timeouts.parse("10000::");
+ Assert.assertEquals(10000L, partial0.minTimeUs);
+ Assert.assertEquals(3600000000L, partial0.minPendingTimeUs);
+ Assert.assertEquals(3600000000L, partial0.maxPendingTimeUs);
+
+ Timeouts partial1 = Timeouts.parse("10000:10001:");
+ Assert.assertEquals(10000L, partial1.minTimeUs);
+ Assert.assertEquals(10001L, partial1.minPendingTimeUs);
+ Assert.assertEquals(3600000000L, partial1.maxPendingTimeUs);
+
+ Timeouts fullDefault = Timeouts.parse("3600000000:3600000000:3600000000");
+ Assert.assertEquals(3600000000L, fullDefault.minTimeUs);
+ Assert.assertEquals(3600000000L, fullDefault.minPendingTimeUs);
+ Assert.assertEquals(3600000000L, fullDefault.maxPendingTimeUs);
+
+ Timeouts full = Timeouts.parse("10000:10001:10002");
+ Assert.assertEquals(10000L, full.minTimeUs);
+ Assert.assertEquals(10001L, full.minPendingTimeUs);
+ Assert.assertEquals(10002L, full.maxPendingTimeUs);
+
+ Timeouts invalid0 = Timeouts.parse(":10000");
+ Assert.assertEquals(3600000000L, invalid0.minTimeUs);
+ Assert.assertEquals(3600000000L, invalid0.minPendingTimeUs);
+ Assert.assertEquals(3600000000L, invalid0.maxPendingTimeUs);
+
+ Timeouts invalid1 = Timeouts.parse(":10000::");
+ Assert.assertEquals(3600000000L, invalid1.minTimeUs);
+ Assert.assertEquals(3600000000L, invalid1.minPendingTimeUs);
+ Assert.assertEquals(3600000000L, invalid1.maxPendingTimeUs);
+
+ Timeouts invalid2 = Timeouts.parse("10000:10001:abcd");
+ Assert.assertEquals(10000L, invalid2.minTimeUs);
+ Assert.assertEquals(10001L, invalid2.minPendingTimeUs);
+ Assert.assertEquals(3600000000L, invalid2.maxPendingTimeUs);
+
+ Timeouts invalid3 = Timeouts.parse(":10000:");
+ Assert.assertEquals(3600000000L, invalid3.minTimeUs);
+ Assert.assertEquals(3600000000L, invalid3.minPendingTimeUs);
+ Assert.assertEquals(3600000000L, invalid3.maxPendingTimeUs);
+
+ Timeouts invalid4 = Timeouts.parse("abcd:10001:10002");
+ Assert.assertEquals(3600000000L, invalid4.minTimeUs);
+ Assert.assertEquals(3600000000L, invalid4.minPendingTimeUs);
+ Assert.assertEquals(3600000000L, invalid4.maxPendingTimeUs);
+
+ Timeouts invalid5 = Timeouts.parse("::1000000000000000000000000");
+ Assert.assertEquals(3600000000L, invalid5.minTimeUs);
+ Assert.assertEquals(3600000000L, invalid5.minPendingTimeUs);
+ Assert.assertEquals(3600000000L, invalid5.maxPendingTimeUs);
+
+ Timeouts invalid6 = Timeouts.parse("-10000:10001:10002");
+ Assert.assertEquals(3600000000L, invalid6.minTimeUs);
+ Assert.assertEquals(3600000000L, invalid6.minPendingTimeUs);
+ Assert.assertEquals(3600000000L, invalid6.maxPendingTimeUs);
+ }
+
+ @Test
+ public void testVersionCodes() {
+ final VersionCodes defaults = VersionCodes.parse("");
+ Assert.assertEquals(Long.MIN_VALUE, defaults.minVersionCode);
+ Assert.assertEquals(Long.MAX_VALUE, defaults.maxVersionCode);
+
+ VersionCodes single = VersionCodes.parse("191000070");
+ Assert.assertEquals(191000070, single.minVersionCode);
+ Assert.assertEquals(191000070, single.maxVersionCode);
+
+ VersionCodes single2 = VersionCodes.parse("191000070-191000070");
+ Assert.assertEquals(191000070, single2.minVersionCode);
+ Assert.assertEquals(191000070, single2.maxVersionCode);
+
+ VersionCodes upto = VersionCodes.parse("-191000070");
+ Assert.assertEquals(Long.MIN_VALUE, upto.minVersionCode);
+ Assert.assertEquals(191000070, upto.maxVersionCode);
+
+ VersionCodes andabove = VersionCodes.parse("191000070-");
+ Assert.assertEquals(191000070, andabove.minVersionCode);
+ Assert.assertEquals(Long.MAX_VALUE, andabove.maxVersionCode);
+
+ VersionCodes range = VersionCodes.parse("191000070-201000070");
+ Assert.assertEquals(191000070, range.minVersionCode);
+ Assert.assertEquals(201000070, range.maxVersionCode);
+
+ VersionCodes invalid0 = VersionCodes.parse("201000070-191000070");
+ Assert.assertEquals(Long.MIN_VALUE, invalid0.minVersionCode);
+ Assert.assertEquals(Long.MAX_VALUE, invalid0.maxVersionCode);
+
+ VersionCodes invalid1 = VersionCodes.parse("abcd-191000070");
+ Assert.assertEquals(Long.MIN_VALUE, invalid1.minVersionCode);
+ Assert.assertEquals(191000070, invalid1.maxVersionCode);
+
+ VersionCodes invalid2 = VersionCodes.parse("abcd");
+ Assert.assertEquals(Long.MIN_VALUE, invalid2.minVersionCode);
+ Assert.assertEquals(Long.MAX_VALUE, invalid2.maxVersionCode);
+
+ VersionCodes invalid3 = VersionCodes.parse("191000070-abcd");
+ Assert.assertEquals(191000070, invalid3.minVersionCode);
+ Assert.assertEquals(Long.MAX_VALUE, invalid3.maxVersionCode);
+ }
+
+ @Test
+ public void testPerPackageReadTimeouts() {
+ final String sha256 = "336faefc91bb2dddf9b21829106fbc607b862132fecd273e1b6b3ea55f09d4e1";
+ final VersionCodes defVCs = VersionCodes.parse("");
+ final Timeouts defTs = Timeouts.parse("3600000001:3600000002:3600000003");
+
+ PerPackageReadTimeouts empty = PerPackageReadTimeouts.parse("", defVCs, defTs);
+ Assert.assertNull(empty);
+
+ PerPackageReadTimeouts packageOnly = PerPackageReadTimeouts.parse("package.com", defVCs,
+ defTs);
+ Assert.assertEquals("package.com", packageOnly.packageName);
+ Assert.assertEquals(null, packageOnly.sha256certificate);
+ Assert.assertEquals(Long.MIN_VALUE, packageOnly.versionCodes.minVersionCode);
+ Assert.assertEquals(Long.MAX_VALUE, packageOnly.versionCodes.maxVersionCode);
+ Assert.assertEquals(3600000001L, packageOnly.timeouts.minTimeUs);
+ Assert.assertEquals(3600000002L, packageOnly.timeouts.minPendingTimeUs);
+ Assert.assertEquals(3600000003L, packageOnly.timeouts.maxPendingTimeUs);
+
+ PerPackageReadTimeouts packageHash = PerPackageReadTimeouts.parse(
+ "package.com:" + sha256, defVCs, defTs);
+ Assert.assertEquals("package.com", packageHash.packageName);
+ Assert.assertEquals(sha256, bytesToHexString(packageHash.sha256certificate));
+ Assert.assertEquals(Long.MIN_VALUE, packageHash.versionCodes.minVersionCode);
+ Assert.assertEquals(Long.MAX_VALUE, packageHash.versionCodes.maxVersionCode);
+ Assert.assertEquals(3600000001L, packageHash.timeouts.minTimeUs);
+ Assert.assertEquals(3600000002L, packageHash.timeouts.minPendingTimeUs);
+ Assert.assertEquals(3600000003L, packageHash.timeouts.maxPendingTimeUs);
+
+ PerPackageReadTimeouts packageVersionCode = PerPackageReadTimeouts.parse(
+ "package.com::191000070", defVCs, defTs);
+ Assert.assertEquals("package.com", packageVersionCode.packageName);
+ Assert.assertEquals(null, packageVersionCode.sha256certificate);
+ Assert.assertEquals(191000070, packageVersionCode.versionCodes.minVersionCode);
+ Assert.assertEquals(191000070, packageVersionCode.versionCodes.maxVersionCode);
+ Assert.assertEquals(3600000001L, packageVersionCode.timeouts.minTimeUs);
+ Assert.assertEquals(3600000002L, packageVersionCode.timeouts.minPendingTimeUs);
+ Assert.assertEquals(3600000003L, packageVersionCode.timeouts.maxPendingTimeUs);
+
+ PerPackageReadTimeouts full = PerPackageReadTimeouts.parse(
+ "package.com:" + sha256 + ":191000070-201000070:10001:10002:10003", defVCs, defTs);
+ Assert.assertEquals("package.com", full.packageName);
+ Assert.assertEquals(sha256, bytesToHexString(full.sha256certificate));
+ Assert.assertEquals(191000070, full.versionCodes.minVersionCode);
+ Assert.assertEquals(201000070, full.versionCodes.maxVersionCode);
+ Assert.assertEquals(10001L, full.timeouts.minTimeUs);
+ Assert.assertEquals(10002L, full.timeouts.minPendingTimeUs);
+ Assert.assertEquals(10003L, full.timeouts.maxPendingTimeUs);
+ }
+
+ @Test
+ public void testGetPerPackageReadTimeouts() {
+ Assert.assertEquals(0, getPerPackageReadTimeouts(null).length);
+ Assert.assertEquals(0, getPerPackageReadTimeouts("").length);
+ Assert.assertEquals(0, getPerPackageReadTimeouts(",,,,").length);
+
+ final String sha256 = "0fae93f1a7925b4c68bbea80ad3eaa41acfc9bc6f10bf1054f5d93a2bd556093";
+
+ PerPackageReadTimeouts[] singlePackage = getPerPackageReadTimeouts(
+ "package.com:" + sha256 + ":191000070-201000070:10001:10002:10003");
+ Assert.assertEquals(1, singlePackage.length);
+ Assert.assertEquals("package.com", singlePackage[0].packageName);
+ Assert.assertEquals(sha256, bytesToHexString(singlePackage[0].sha256certificate));
+ Assert.assertEquals(191000070, singlePackage[0].versionCodes.minVersionCode);
+ Assert.assertEquals(201000070, singlePackage[0].versionCodes.maxVersionCode);
+ Assert.assertEquals(10001L, singlePackage[0].timeouts.minTimeUs);
+ Assert.assertEquals(10002L, singlePackage[0].timeouts.minPendingTimeUs);
+ Assert.assertEquals(10003L, singlePackage[0].timeouts.maxPendingTimeUs);
+
+ PerPackageReadTimeouts[] multiPackage = getPerPackageReadTimeouts("package.com:" + sha256
+ + ":191000070-201000070:10001:10002:10003,package1.com::123456");
+ Assert.assertEquals(2, multiPackage.length);
+ Assert.assertEquals("package.com", multiPackage[0].packageName);
+ Assert.assertEquals(sha256, bytesToHexString(multiPackage[0].sha256certificate));
+ Assert.assertEquals(191000070, multiPackage[0].versionCodes.minVersionCode);
+ Assert.assertEquals(201000070, multiPackage[0].versionCodes.maxVersionCode);
+ Assert.assertEquals(10001L, multiPackage[0].timeouts.minTimeUs);
+ Assert.assertEquals(10002L, multiPackage[0].timeouts.minPendingTimeUs);
+ Assert.assertEquals(10003L, multiPackage[0].timeouts.maxPendingTimeUs);
+ Assert.assertEquals("package1.com", multiPackage[1].packageName);
+ Assert.assertEquals(null, multiPackage[1].sha256certificate);
+ Assert.assertEquals(123456, multiPackage[1].versionCodes.minVersionCode);
+ Assert.assertEquals(123456, multiPackage[1].versionCodes.maxVersionCode);
+ Assert.assertEquals(3600000001L, multiPackage[1].timeouts.minTimeUs);
+ Assert.assertEquals(3600000002L, multiPackage[1].timeouts.minPendingTimeUs);
+ Assert.assertEquals(3600000003L, multiPackage[1].timeouts.maxPendingTimeUs);
+ }
+
+ private static PerPackageReadTimeouts[] getPerPackageReadTimeouts(String knownDigestersList) {
+ final String defaultTimeouts = "3600000001:3600000002:3600000003";
+ List<PerPackageReadTimeouts> result = PerPackageReadTimeouts.parseDigestersList(
+ defaultTimeouts, knownDigestersList);
+ if (result == null) {
+ return null;
+ }
+ return result.toArray(new PerPackageReadTimeouts[result.size()]);
+ }
+
+ private static String bytesToHexString(byte[] bytes) {
+ return HexDump.toHexString(bytes, 0, bytes.length, /*upperCase=*/ false);
+ }
+
private List<Integer> getKnownPackageIdsList() throws IllegalAccessException {
final ArrayList<Integer> knownPackageIds = new ArrayList<>();
final Field[] allFields = PackageManagerInternal.class.getDeclaredFields();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
index 21aa6bf..fc96b69 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -156,8 +156,10 @@
organizer.setMoveToSecondaryOnEnter(false);
// Create primary splitscreen stack.
- final Task primarySplitScreen = mDefaultTaskDisplayArea.createRootTask(
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ final Task primarySplitScreen = new TaskBuilder(mAtm.mTaskSupervisor)
+ .setParentTask(organizer.mPrimary)
+ .setOnTop(true)
+ .build();
// Assert windowing mode.
assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, primarySplitScreen.getWindowingMode());
@@ -205,14 +207,15 @@
@Test
public void testSplitScreenMoveToBack() {
TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm);
- // Set up split-screen with primary on top and secondary containing the home task below
- // another stack.
+ // Explicitly reparent task to primary split root to enter split mode, in which implies
+ // primary on top and secondary containing the home task below another stack.
final Task primaryTask = mDefaultTaskDisplayArea.createRootTask(
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ final Task secondaryTask = mDefaultTaskDisplayArea.createRootTask(
+ WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
final Task homeRoot = mDefaultTaskDisplayArea.getRootTask(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME);
- final Task secondaryTask = mDefaultTaskDisplayArea.createRootTask(
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ primaryTask.reparent(organizer.mPrimary, POSITION_TOP);
mDefaultTaskDisplayArea.positionChildAt(POSITION_TOP, organizer.mPrimary,
false /* includingParents */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index e8045e0..4bfc837 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -485,8 +485,10 @@
final ActivityRecord splitSecondActivity =
new ActivityBuilder(mAtm).setCreateTask(true).build();
final ActivityRecord splitPrimaryActivity = new TaskBuilder(mSupervisor)
- .setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY).setCreateActivity(true)
- .build().getTopMostActivity();
+ .setParentTask(splitOrg.mPrimary)
+ .setCreateActivity(true)
+ .build()
+ .getTopMostActivity();
splitPrimaryActivity.mVisibleRequested = splitSecondActivity.mVisibleRequested = true;
assertEquals(splitOrg.mPrimary, splitPrimaryActivity.getRootTask());
@@ -720,6 +722,7 @@
}
// caller is instrumenting with background activity starts privileges
callerApp.setInstrumenting(callerIsInstrumentingWithBackgroundActivityStartPrivileges,
+ callerIsInstrumentingWithBackgroundActivityStartPrivileges ? Process.SHELL_UID : -1,
callerIsInstrumentingWithBackgroundActivityStartPrivileges);
// callingUid is the device owner
doReturn(isCallingUidDeviceOwner).when(mAtm).isDeviceOwner(callingUid);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
index 475e462..009c011 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
@@ -31,13 +31,17 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import android.app.WaitResult;
+import android.content.ComponentName;
import android.content.pm.ActivityInfo;
+import android.os.ConditionVariable;
import android.platform.test.annotations.Presubmit;
import android.view.Display;
@@ -58,6 +62,7 @@
@Presubmit
@RunWith(WindowTestRunner.class)
public class ActivityTaskSupervisorTests extends WindowTestsBase {
+ private static final long TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);
/**
* Ensures that an activity is removed from the stopping activities list once it is resumed.
@@ -74,30 +79,72 @@
}
/**
- * Ensures that waiting results are notified of launches.
+ * Assume an activity has been started with result code START_SUCCESS. And before it is drawn,
+ * it launches another existing activity. This test ensures that waiting results are notified
+ * or updated while the result code of next launch is TASK_TO_FRONT or DELIVERED_TO_TOP.
*/
@Test
- public void testReportWaitingActivityLaunchedIfNeeded() {
+ public void testReportWaitingActivityLaunched() {
final ActivityRecord firstActivity = new ActivityBuilder(mAtm)
.setCreateTask(true).build();
-
+ final ActivityRecord secondActivity = new ActivityBuilder(mAtm)
+ .setCreateTask(true).build();
+ final ConditionVariable condition = new ConditionVariable();
final WaitResult taskToFrontWait = new WaitResult();
- mSupervisor.mWaitingActivityLaunched.add(taskToFrontWait);
- // #notifyAll will be called on the ActivityTaskManagerService#mGlobalLock. The lock is hold
- // implicitly by WindowManagerGlobalLockRule.
- mSupervisor.reportWaitingActivityLaunchedIfNeeded(firstActivity, START_TASK_TO_FRONT);
+ final ComponentName[] launchedComponent = { null };
+ // Create a new thread so the waiting method in test can be notified.
+ new Thread(() -> {
+ synchronized (mAtm.mGlobalLock) {
+ // Note that TASK_TO_FRONT doesn't unblock the waiting thread.
+ mSupervisor.reportWaitingActivityLaunchedIfNeeded(firstActivity,
+ START_TASK_TO_FRONT);
+ launchedComponent[0] = taskToFrontWait.who;
+ // Assume that another task is brought to front because first activity launches it.
+ mSupervisor.reportActivityLaunched(false /* timeout */, secondActivity,
+ 100 /* totalTime */, WaitResult.LAUNCH_STATE_HOT);
+ }
+ condition.open();
+ }).start();
+ final ActivityMetricsLogger.LaunchingState launchingState =
+ new ActivityMetricsLogger.LaunchingState();
+ spyOn(launchingState);
+ doReturn(true).when(launchingState).contains(eq(secondActivity));
+ // The test case already runs inside global lock, so above thread can only execute after
+ // this waiting method that releases the lock.
+ mSupervisor.waitActivityVisibleOrLaunched(taskToFrontWait, firstActivity, launchingState);
- assertThat(mSupervisor.mWaitingActivityLaunched).isEmpty();
+ // Assert that the thread is finished.
+ assertTrue(condition.block(TIMEOUT_MS));
assertEquals(taskToFrontWait.result, START_TASK_TO_FRONT);
- assertNull(taskToFrontWait.who);
+ assertEquals(taskToFrontWait.who, secondActivity.mActivityComponent);
+ assertEquals(taskToFrontWait.launchState, WaitResult.LAUNCH_STATE_HOT);
+ // START_TASK_TO_FRONT means that another component will be visible, so the component
+ // should not be assigned as the first activity.
+ assertNull(launchedComponent[0]);
+ condition.close();
final WaitResult deliverToTopWait = new WaitResult();
- mSupervisor.mWaitingActivityLaunched.add(deliverToTopWait);
- mSupervisor.reportWaitingActivityLaunchedIfNeeded(firstActivity, START_DELIVERED_TO_TOP);
+ new Thread(() -> {
+ synchronized (mAtm.mGlobalLock) {
+ // Put a noise which isn't tracked by the current wait result. The waiting procedure
+ // should ignore it and keep waiting for the target activity.
+ mSupervisor.reportActivityLaunched(false /* timeout */, mock(ActivityRecord.class),
+ 1000 /* totalTime */, WaitResult.LAUNCH_STATE_COLD);
+ // Assume that the first activity launches an existing top activity, so the waiting
+ // thread should be unblocked.
+ mSupervisor.reportWaitingActivityLaunchedIfNeeded(secondActivity,
+ START_DELIVERED_TO_TOP);
+ }
+ condition.open();
+ }).start();
+ mSupervisor.waitActivityVisibleOrLaunched(deliverToTopWait, firstActivity, launchingState);
- assertThat(mSupervisor.mWaitingActivityLaunched).isEmpty();
+ assertTrue(condition.block(TIMEOUT_MS));
assertEquals(deliverToTopWait.result, START_DELIVERED_TO_TOP);
- assertEquals(deliverToTopWait.who, firstActivity.mActivityComponent);
+ assertEquals(deliverToTopWait.who, secondActivity.mActivityComponent);
+ // The result state must be unknown because DELIVERED_TO_TOP means that the target activity
+ // is already visible so there is no valid launch time.
+ assertEquals(deliverToTopWait.launchState, WaitResult.LAUNCH_STATE_UNKNOWN);
}
/**
@@ -202,7 +249,6 @@
public void testStartHomeAfterUserUnlocked() {
mSupervisor.onUserUnlocked(0);
waitHandlerIdle(mAtm.mH);
- verify(mRootWindowContainer, timeout(TimeUnit.SECONDS.toMillis(10)))
- .startHomeOnEmptyDisplays("userUnlocked");
+ verify(mRootWindowContainer, timeout(TIMEOUT_MS)).startHomeOnEmptyDisplays("userUnlocked");
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 31e2dce..8b93372 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -17,6 +17,8 @@
package com.android.server.wm;
import static android.app.AppOpsManager.OP_NONE;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
@@ -1107,6 +1109,17 @@
// moves everything to secondary. Most tests expect this since sysui usually does it.
boolean mMoveToSecondaryOnEnter = true;
int mDisplayId;
+ private static final int[] CONTROLLED_ACTIVITY_TYPES = {
+ ACTIVITY_TYPE_STANDARD,
+ ACTIVITY_TYPE_HOME,
+ ACTIVITY_TYPE_RECENTS,
+ ACTIVITY_TYPE_UNDEFINED
+ };
+ private static final int[] CONTROLLED_WINDOWING_MODES = {
+ WINDOWING_MODE_FULLSCREEN,
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
+ WINDOWING_MODE_UNDEFINED
+ };
TestSplitOrganizer(ActivityTaskManagerService service, DisplayContent display) {
mService = service;
mDisplayId = display.mDisplayId;
@@ -1151,9 +1164,9 @@
if (!mMoveToSecondaryOnEnter) {
return;
}
- mService.mTaskOrganizerController.setLaunchRoot(mDisplayId,
- mSecondary.mRemoteToken.toWindowContainerToken());
DisplayContent dc = mService.mRootWindowContainer.getDisplayContent(mDisplayId);
+ dc.getDefaultTaskDisplayArea().setLaunchRootTask(
+ mSecondary, CONTROLLED_WINDOWING_MODES, CONTROLLED_ACTIVITY_TYPES);
dc.forAllRootTasks(rootTask -> {
if (!WindowConfiguration.isSplitScreenWindowingMode(rootTask.getWindowingMode())) {
rootTask.reparent(mSecondary, POSITION_BOTTOM);
diff --git a/services/translation/Android.bp b/services/translation/Android.bp
new file mode 100644
index 0000000..804a617
--- /dev/null
+++ b/services/translation/Android.bp
@@ -0,0 +1,13 @@
+filegroup {
+ name: "services.translation-sources",
+ srcs: ["java/**/*.java"],
+ path: "java",
+ visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+ name: "services.translation",
+ defaults: ["platform_service_defaults"],
+ srcs: [":services.translation-sources"],
+ libs: ["services.core"],
+}
\ No newline at end of file
diff --git a/services/translation/OWNERS b/services/translation/OWNERS
new file mode 100644
index 0000000..a1e663a
--- /dev/null
+++ b/services/translation/OWNERS
@@ -0,0 +1,8 @@
+# Bug component: 994311
+
+adamhe@google.com
+augale@google.com
+joannechung@google.com
+lpeter@google.com
+svetoslavganov@google.com
+tymtsai@google.com
diff --git a/services/translation/java/com/android/server/translation/RemoteTranslationService.java b/services/translation/java/com/android/server/translation/RemoteTranslationService.java
new file mode 100644
index 0000000..0c7e617
--- /dev/null
+++ b/services/translation/java/com/android/server/translation/RemoteTranslationService.java
@@ -0,0 +1,87 @@
+/*
+ * 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.translation;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.service.translation.ITranslationService;
+import android.service.translation.TranslationService;
+import android.util.Slog;
+import android.view.translation.TranslationSpec;
+
+import com.android.internal.infra.AbstractRemoteService;
+import com.android.internal.infra.ServiceConnector;
+import com.android.internal.os.IResultReceiver;
+
+final class RemoteTranslationService extends ServiceConnector.Impl<ITranslationService> {
+
+ private static final String TAG = RemoteTranslationService.class.getSimpleName();
+
+ // TODO(b/176590870): Make PERMANENT now.
+ private static final long TIMEOUT_IDLE_UNBIND_MS =
+ AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS;
+ private static final int TIMEOUT_REQUEST_MS = 5_000;
+
+ private final long mIdleUnbindTimeoutMs;
+ private final int mRequestTimeoutMs;
+ private final ComponentName mComponentName;
+
+ RemoteTranslationService(Context context, ComponentName serviceName,
+ int userId, boolean bindInstantServiceAllowed) {
+ super(context,
+ new Intent(TranslationService.SERVICE_INTERFACE).setComponent(serviceName),
+ bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0,
+ userId, ITranslationService.Stub::asInterface);
+ mIdleUnbindTimeoutMs = TIMEOUT_IDLE_UNBIND_MS;
+ mRequestTimeoutMs = TIMEOUT_REQUEST_MS;
+ mComponentName = serviceName;
+
+ // Bind right away.
+ connect();
+ }
+
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
+ @Override // from ServiceConnector.Impl
+ protected void onServiceConnectionStatusChanged(ITranslationService service,
+ boolean connected) {
+ try {
+ if (connected) {
+ service.onConnected();
+ } else {
+ service.onDisconnected();
+ }
+ } catch (Exception e) {
+ Slog.w(TAG,
+ "Exception calling onServiceConnectionStatusChanged(" + connected + "): ", e);
+ }
+ }
+
+ @Override // from AbstractRemoteService
+ protected long getAutoDisconnectTimeoutMs() {
+ return mIdleUnbindTimeoutMs;
+ }
+
+ public void onSessionCreated(@NonNull TranslationSpec sourceSpec,
+ @NonNull TranslationSpec destSpec, int sessionId, IResultReceiver resultReceiver) {
+ run((s) -> s.onCreateTranslationSession(sourceSpec, destSpec, sessionId, resultReceiver));
+ }
+}
diff --git a/services/translation/java/com/android/server/translation/TranslationManagerService.java b/services/translation/java/com/android/server/translation/TranslationManagerService.java
new file mode 100644
index 0000000..e2aabe6
--- /dev/null
+++ b/services/translation/java/com/android/server/translation/TranslationManagerService.java
@@ -0,0 +1,92 @@
+/*
+ * 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.translation;
+
+import static android.content.Context.TRANSLATION_MANAGER_SERVICE;
+import static android.view.translation.TranslationManager.STATUS_SYNC_CALL_FAIL;
+
+import android.content.Context;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.view.translation.ITranslationManager;
+import android.view.translation.TranslationSpec;
+
+import com.android.internal.os.IResultReceiver;
+import com.android.server.infra.AbstractMasterSystemService;
+import com.android.server.infra.FrameworkResourcesServiceNameResolver;
+
+/**
+ * Entry point service for translation management.
+ *
+ * <p>This service provides the {@link ITranslationManager} implementation and keeps a list of
+ * {@link TranslationManagerServiceImpl} per user; the real work is done by
+ * {@link TranslationManagerServiceImpl} itself.
+ */
+public final class TranslationManagerService
+ extends AbstractMasterSystemService<TranslationManagerService,
+ TranslationManagerServiceImpl> {
+
+ private static final String TAG = "TranslationManagerService";
+
+ public TranslationManagerService(Context context) {
+ // TODO: Discuss the disallow policy
+ super(context, new FrameworkResourcesServiceNameResolver(context,
+ com.android.internal.R.string.config_defaultTranslationService),
+ /* disallowProperty */ null, PACKAGE_UPDATE_POLICY_REFRESH_EAGER);
+ }
+
+ @Override
+ protected TranslationManagerServiceImpl newServiceLocked(int resolvedUserId, boolean disabled) {
+ return new TranslationManagerServiceImpl(this, mLock, resolvedUserId, disabled);
+ }
+
+ final class TranslationManagerServiceStub extends ITranslationManager.Stub {
+ @Override
+ public void getSupportedLocales(IResultReceiver receiver, int userId)
+ throws RemoteException {
+ synchronized (mLock) {
+ final TranslationManagerServiceImpl service = getServiceForUserLocked(userId);
+ if (service != null) {
+ service.getSupportedLocalesLocked(receiver);
+ } else {
+ Slog.v(TAG, "getSupportedLocales(): no service for " + userId);
+ receiver.send(STATUS_SYNC_CALL_FAIL, null);
+ }
+ }
+ }
+
+ @Override
+ public void onSessionCreated(TranslationSpec sourceSpec, TranslationSpec destSpec,
+ int sessionId, IResultReceiver receiver, int userId) throws RemoteException {
+ synchronized (mLock) {
+ final TranslationManagerServiceImpl service = getServiceForUserLocked(userId);
+ if (service != null) {
+ service.onSessionCreatedLocked(sourceSpec, destSpec, sessionId, receiver);
+ } else {
+ Slog.v(TAG, "onSessionCreated(): no service for " + userId);
+ receiver.send(STATUS_SYNC_CALL_FAIL, null);
+ }
+ }
+ }
+ }
+
+ @Override // from SystemService
+ public void onStart() {
+ publishBinderService(TRANSLATION_MANAGER_SERVICE,
+ new TranslationManagerService.TranslationManagerServiceStub());
+ }
+}
diff --git a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
new file mode 100644
index 0000000..b1f6f80
--- /dev/null
+++ b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
@@ -0,0 +1,125 @@
+/*
+ * 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.translation;
+
+import static android.view.translation.TranslationManager.STATUS_SYNC_CALL_SUCCESS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.RemoteException;
+import android.service.translation.TranslationServiceInfo;
+import android.util.Slog;
+import android.view.translation.TranslationSpec;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.IResultReceiver;
+import com.android.internal.util.SyncResultReceiver;
+import com.android.server.infra.AbstractPerUserSystemService;
+
+import java.util.ArrayList;
+
+final class TranslationManagerServiceImpl extends
+ AbstractPerUserSystemService<TranslationManagerServiceImpl, TranslationManagerService> {
+
+ private static final String TAG = "TranslationManagerServiceImpl";
+
+ @GuardedBy("mLock")
+ @Nullable
+ private RemoteTranslationService mRemoteTranslationService;
+
+ @GuardedBy("mLock")
+ @Nullable
+ private ServiceInfo mRemoteTranslationServiceInfo;
+
+ protected TranslationManagerServiceImpl(
+ @NonNull TranslationManagerService master,
+ @NonNull Object lock, int userId, boolean disabled) {
+ super(master, lock, userId);
+ updateRemoteServiceLocked();
+ }
+
+ @GuardedBy("mLock")
+ @Override // from PerUserSystemService
+ protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
+ throws PackageManager.NameNotFoundException {
+ final TranslationServiceInfo info = new TranslationServiceInfo(getContext(),
+ serviceComponent, isTemporaryServiceSetLocked(), mUserId);
+ mRemoteTranslationServiceInfo = info.getServiceInfo();
+ return info.getServiceInfo();
+ }
+
+ @GuardedBy("mLock")
+ @Override // from PerUserSystemService
+ protected boolean updateLocked(boolean disabled) {
+ final boolean enabledChanged = super.updateLocked(disabled);
+ updateRemoteServiceLocked();
+ return enabledChanged;
+ }
+
+ /**
+ * Updates the reference to the remote service.
+ */
+ @GuardedBy("mLock")
+ private void updateRemoteServiceLocked() {
+ if (mRemoteTranslationService != null) {
+ if (mMaster.debug) Slog.d(TAG, "updateRemoteService(): destroying old remote service");
+ mRemoteTranslationService.unbind();
+ mRemoteTranslationService = null;
+ }
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ private RemoteTranslationService ensureRemoteServiceLocked() {
+ if (mRemoteTranslationService == null) {
+ final String serviceName = getComponentNameLocked();
+ if (serviceName == null) {
+ if (mMaster.verbose) {
+ Slog.v(TAG, "ensureRemoteServiceLocked(): no service component name.");
+ }
+ return null;
+ }
+ final ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
+ mRemoteTranslationService = new RemoteTranslationService(getContext(),
+ serviceComponent, mUserId, /* isInstantAllowed= */ false);
+ }
+ return mRemoteTranslationService;
+ }
+
+ @GuardedBy("mLock")
+ void getSupportedLocalesLocked(@NonNull IResultReceiver resultReceiver) {
+ // TODO: implement this
+ try {
+ resultReceiver.send(STATUS_SYNC_CALL_SUCCESS,
+ SyncResultReceiver.bundleFor(new ArrayList<>()));
+ } catch (RemoteException e) {
+ Slog.w(TAG, "RemoteException returning supported locales: " + e);
+ }
+ }
+
+ @GuardedBy("mLock")
+ void onSessionCreatedLocked(@NonNull TranslationSpec sourceSpec,
+ @NonNull TranslationSpec destSpec, int sessionId, IResultReceiver resultReceiver) {
+ final RemoteTranslationService remoteService = ensureRemoteServiceLocked();
+ if (remoteService != null) {
+ remoteService.onSessionCreated(sourceSpec, destSpec, sessionId, resultReceiver);
+ }
+ }
+}
diff --git a/telephony/java/android/telephony/CarrierBandwidth.java b/telephony/java/android/telephony/CarrierBandwidth.java
index 17747a3..b153fef 100644
--- a/telephony/java/android/telephony/CarrierBandwidth.java
+++ b/telephony/java/android/telephony/CarrierBandwidth.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresFeature;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -102,7 +103,7 @@
/**
* Retrieves the upstream bandwidth for the primary network in Kbps. This always only refers to
* the estimated first hop transport bandwidth.
- * This will be INVALID if the network is not connected
+ * This will be {@link #INVALID} if the network is not connected
*
* @return The estimated first hop upstream (device to network) bandwidth.
*/
@@ -113,7 +114,7 @@
/**
* Retrieves the downstream bandwidth for the primary network in Kbps. This always only refers
* to the estimated first hop transport bandwidth.
- * This will be INVALID if the network is not connected
+ * This will be {@link #INVALID} if the network is not connected
*
* @return The estimated first hop downstream (network to device) bandwidth.
*/
@@ -124,10 +125,19 @@
/**
* Retrieves the upstream bandwidth for the secondary network in Kbps. This always only refers
* to the estimated first hop transport bandwidth.
- * This will be INVALID if the network is not connected
+ * <p/>
+ * This will be {@link #INVALID} if either are the case:
+ * <ol>
+ * <li>The network is not connected</li>
+ * <li>The device does not support
+ * {@link android.telephony.TelephonyManager#CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE}.</li>
+ * </ol>
*
* @return The estimated first hop upstream (device to network) bandwidth.
*/
+ @RequiresFeature(
+ enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported",
+ value = TelephonyManager.CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE)
public int getSecondaryDownlinkCapacityKbps() {
return mSecondaryDownlinkCapacityKbps;
}
@@ -135,10 +145,18 @@
/**
* Retrieves the downstream bandwidth for the secondary network in Kbps. This always only
* refers to the estimated first hop transport bandwidth.
- * This will be INVALID if the network is not connected
- *
+ * <p/>
+ * This will be {@link #INVALID} if either are the case:
+ * <ol>
+ * <li>The network is not connected</li>
+ * <li>The device does not support
+ * {@link android.telephony.TelephonyManager#CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE}.</li>
+ * </ol>
* @return The estimated first hop downstream (network to device) bandwidth.
*/
+ @RequiresFeature(
+ enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported",
+ value = TelephonyManager.CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE)
public int getSecondaryUplinkCapacityKbps() {
return mSecondaryUplinkCapacityKbps;
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index a6e870f..a10d353 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -32,6 +32,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.StringDef;
import android.annotation.SuppressAutoDoc;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
@@ -14430,10 +14431,22 @@
return Collections.emptyList();
}
+ /**
+ * Indicates whether {@link CarrierBandwidth#getSecondaryDownlinkCapacityKbps()} and
+ * {@link CarrierBandwidth#getSecondaryUplinkCapacityKbps()} are visible. See comments
+ * on respective methods for more information.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE =
+ "CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE";
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = {"RADIO_INTERFACE_CAPABILITY_"},
- value = {})
+ @StringDef(prefix = "CAPABILITY_", value = {
+ CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE,
+ })
public @interface RadioInterfaceCapability {}
/**
@@ -14446,6 +14459,7 @@
*
* @hide
*/
+ @SystemApi
public boolean isRadioInterfaceCapabilitySupported(
@NonNull @RadioInterfaceCapability String capability) {
try {
diff --git a/tests/net/Android.bp b/tests/net/Android.bp
index a762219..f6a2846 100644
--- a/tests/net/Android.bp
+++ b/tests/net/Android.bp
@@ -70,4 +70,7 @@
"android.test.base",
"android.test.mock",
],
+ jni_libs: [
+ "libservice-connectivity",
+ ],
}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 3433880..26b764f 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -1199,6 +1199,8 @@
updateState(NetworkInfo.DetailedState.DISCONNECTED, "disconnect");
}
mAgentRegistered = false;
+ setUids(null);
+ mInterface = null;
}
@Override
@@ -3361,6 +3363,7 @@
assertEquals(null, mCm.getActiveNetwork());
mMockVpn.establishForMyUid();
+ assertUidRangesUpdatedForMyUid(true);
defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
@@ -3624,51 +3627,55 @@
// Register the factory and expect it to start looking for a network.
testFactory.expectAddRequestsWithScores(0); // Score 0 as the request is not served yet.
testFactory.register();
- testFactory.waitForNetworkRequests(1);
- assertTrue(testFactory.getMyStartRequested());
- // Bring up wifi. The factory stops looking for a network.
- mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
- // Score 60 - 40 penalty for not validated yet, then 60 when it validates
- testFactory.expectAddRequestsWithScores(20, 60);
- mWiFiNetworkAgent.connect(true);
- testFactory.waitForRequests();
- assertFalse(testFactory.getMyStartRequested());
+ try {
+ testFactory.waitForNetworkRequests(1);
+ assertTrue(testFactory.getMyStartRequested());
- ContentResolver cr = mServiceContext.getContentResolver();
+ // Bring up wifi. The factory stops looking for a network.
+ mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+ // Score 60 - 40 penalty for not validated yet, then 60 when it validates
+ testFactory.expectAddRequestsWithScores(20, 60);
+ mWiFiNetworkAgent.connect(true);
+ testFactory.waitForRequests();
+ assertFalse(testFactory.getMyStartRequested());
- // Turn on mobile data always on. The factory starts looking again.
- testFactory.expectAddRequestsWithScores(0); // Always on requests comes up with score 0
- setAlwaysOnNetworks(true);
- testFactory.waitForNetworkRequests(2);
- assertTrue(testFactory.getMyStartRequested());
+ ContentResolver cr = mServiceContext.getContentResolver();
- // Bring up cell data and check that the factory stops looking.
- assertLength(1, mCm.getAllNetworks());
- mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
- testFactory.expectAddRequestsWithScores(10, 50); // Unvalidated, then validated
- mCellNetworkAgent.connect(true);
- cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
- testFactory.waitForNetworkRequests(2);
- assertFalse(testFactory.getMyStartRequested()); // Because the cell network outscores us.
+ // Turn on mobile data always on. The factory starts looking again.
+ testFactory.expectAddRequestsWithScores(0); // Always on requests comes up with score 0
+ setAlwaysOnNetworks(true);
+ testFactory.waitForNetworkRequests(2);
+ assertTrue(testFactory.getMyStartRequested());
- // Check that cell data stays up.
- waitForIdle();
- verifyActiveNetwork(TRANSPORT_WIFI);
- assertLength(2, mCm.getAllNetworks());
+ // Bring up cell data and check that the factory stops looking.
+ assertLength(1, mCm.getAllNetworks());
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ testFactory.expectAddRequestsWithScores(10, 50); // Unvalidated, then validated
+ mCellNetworkAgent.connect(true);
+ cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ testFactory.waitForNetworkRequests(2);
+ assertFalse(
+ testFactory.getMyStartRequested()); // Because the cell network outscores us.
- // Turn off mobile data always on and expect the request to disappear...
- testFactory.expectRemoveRequests(1);
- setAlwaysOnNetworks(false);
- testFactory.waitForNetworkRequests(1);
+ // Check that cell data stays up.
+ waitForIdle();
+ verifyActiveNetwork(TRANSPORT_WIFI);
+ assertLength(2, mCm.getAllNetworks());
- // ... and cell data to be torn down.
- cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
- assertLength(1, mCm.getAllNetworks());
+ // Turn off mobile data always on and expect the request to disappear...
+ testFactory.expectRemoveRequests(1);
+ setAlwaysOnNetworks(false);
+ testFactory.waitForNetworkRequests(1);
- testFactory.terminate();
- mCm.unregisterNetworkCallback(cellNetworkCallback);
- handlerThread.quit();
+ // ... and cell data to be torn down.
+ cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+ assertLength(1, mCm.getAllNetworks());
+ } finally {
+ testFactory.terminate();
+ mCm.unregisterNetworkCallback(cellNetworkCallback);
+ handlerThread.quit();
+ }
}
@Test
@@ -5047,6 +5054,7 @@
lp.setInterfaceName(VPN_IFNAME);
mMockVpn.establishForMyUid(lp);
+ assertUidRangesUpdatedForMyUid(true);
final Network[] cellAndVpn = new Network[] {
mCellNetworkAgent.getNetwork(), mMockVpn.getNetwork()};
@@ -5632,6 +5640,7 @@
// (and doing so is difficult without using reflection) but it's good to test that the code
// behaves approximately correctly.
mMockVpn.establishForMyUid(false, true, false);
+ assertUidRangesUpdatedForMyUid(true);
final Network wifiNetwork = new Network(mNetIdManager.peekNextNetId());
mService.setUnderlyingNetworksForVpn(new Network[]{wifiNetwork});
callback.expectAvailableCallbacksUnvalidated(mMockVpn);
@@ -5789,6 +5798,7 @@
mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */,
false /* isStrictMode */);
+ assertUidRangesUpdatedForMyUid(true);
defaultCallback.assertNoCallback();
assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
@@ -5814,6 +5824,7 @@
mMockVpn.establishForMyUid(true /* validated */, true /* hasInternet */,
false /* isStrictMode */);
+ assertUidRangesUpdatedForMyUid(true);
defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
@@ -5839,6 +5850,7 @@
// Bring up a VPN that has the INTERNET capability, initially unvalidated.
mMockVpn.establishForMyUid(false /* validated */, true /* hasInternet */,
false /* isStrictMode */);
+ assertUidRangesUpdatedForMyUid(true);
// Even though the VPN is unvalidated, it becomes the default network for our app.
callback.expectAvailableCallbacksUnvalidated(mMockVpn);
@@ -5890,6 +5902,7 @@
mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */,
false /* isStrictMode */);
+ assertUidRangesUpdatedForMyUid(true);
vpnNetworkCallback.expectAvailableCallbacks(mMockVpn.getNetwork(),
false /* suspended */, false /* validated */, false /* blocked */, TIMEOUT_MS);
@@ -5931,6 +5944,7 @@
mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */,
false /* isStrictMode */);
+ assertUidRangesUpdatedForMyUid(true);
vpnNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork());
@@ -6098,6 +6112,7 @@
mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */,
false /* isStrictMode */);
+ assertUidRangesUpdatedForMyUid(true);
vpnNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork());
@@ -6156,6 +6171,7 @@
// Bring up a VPN
mMockVpn.establishForMyUid();
+ assertUidRangesUpdatedForMyUid(true);
callback.expectAvailableThenValidatedCallbacks(mMockVpn);
callback.assertNoCallback();
@@ -6176,11 +6192,15 @@
// Create a fake restricted profile whose parent is our user ID.
final int userId = UserHandle.getUserId(uid);
+ when(mUserManager.canHaveRestrictedProfile(userId)).thenReturn(true);
final int restrictedUserId = userId + 1;
final UserInfo info = new UserInfo(restrictedUserId, "user", UserInfo.FLAG_RESTRICTED);
info.restrictedProfileParentId = userId;
assertTrue(info.isRestricted());
when(mUserManager.getUserInfo(restrictedUserId)).thenReturn(info);
+ when(mPackageManager.getPackageUidAsUser(ALWAYS_ON_PACKAGE, restrictedUserId))
+ .thenReturn(UserHandle.getUid(restrictedUserId, VPN_UID));
+
final Intent addedIntent = new Intent(ACTION_USER_ADDED);
addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, restrictedUserId);
@@ -6220,6 +6240,54 @@
&& caps.getUids().contains(new UidRange(uid, uid))
&& caps.hasTransport(TRANSPORT_VPN)
&& !caps.hasTransport(TRANSPORT_WIFI));
+
+ // Test lockdown with restricted profiles.
+ mServiceContext.setPermission(
+ Manifest.permission.CONTROL_ALWAYS_ON_VPN, PERMISSION_GRANTED);
+ mServiceContext.setPermission(
+ Manifest.permission.CONTROL_VPN, PERMISSION_GRANTED);
+ mServiceContext.setPermission(
+ Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
+
+ // Connect wifi and check that UIDs in the main and restricted profiles have network access.
+ mMockVpn.disconnect();
+ mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(true /* validated */);
+ final int restrictedUid = UserHandle.getUid(restrictedUserId, 42 /* appId */);
+ assertNotNull(mCm.getActiveNetworkForUid(uid));
+ assertNotNull(mCm.getActiveNetworkForUid(restrictedUid));
+
+ // Enable always-on VPN lockdown. The main user loses network access because no VPN is up.
+ final ArrayList<String> allowList = new ArrayList<>();
+ mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList);
+ waitForIdle();
+ assertNull(mCm.getActiveNetworkForUid(uid));
+ assertNotNull(mCm.getActiveNetworkForUid(restrictedUid));
+
+ // Start the restricted profile, and check that the UID within it loses network access.
+ when(mUserManager.getAliveUsers()).thenReturn(
+ Arrays.asList(new UserInfo[] {
+ new UserInfo(userId, "", 0),
+ info
+ }));
+ // TODO: check that VPN app within restricted profile still has access, etc.
+ handler.post(() -> mServiceContext.sendBroadcast(addedIntent));
+ waitForIdle();
+ assertNull(mCm.getActiveNetworkForUid(uid));
+ assertNull(mCm.getActiveNetworkForUid(restrictedUid));
+
+ // Stop the restricted profile, and check that the UID within it has network access again.
+ when(mUserManager.getAliveUsers()).thenReturn(
+ Arrays.asList(new UserInfo[] {
+ new UserInfo(userId, "", 0),
+ }));
+ handler.post(() -> mServiceContext.sendBroadcast(removedIntent));
+ waitForIdle();
+ assertNull(mCm.getActiveNetworkForUid(uid));
+ assertNotNull(mCm.getActiveNetworkForUid(restrictedUid));
+
+ mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
+ waitForIdle();
}
@Test
@@ -6258,6 +6326,7 @@
// Connect VPN network. By default it is using current default network (Cell).
mMockVpn.establishForMyUid();
+ assertUidRangesUpdatedForMyUid(true);
// Ensure VPN is now the active network.
assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork());
@@ -6310,6 +6379,7 @@
// Connect VPN network.
mMockVpn.establishForMyUid();
+ assertUidRangesUpdatedForMyUid(true);
// Ensure VPN is now the active network.
assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork());
@@ -6684,6 +6754,7 @@
assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
mMockVpn.establishForMyUid();
+ assertUidRangesUpdatedForMyUid(true);
defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
vpnUidCallback.assertNoCallback(); // vpnUidCallback has NOT_VPN capability.
assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork());
@@ -7341,6 +7412,7 @@
LinkProperties testLinkProperties = new LinkProperties();
testLinkProperties.setHttpProxy(testProxyInfo);
mMockVpn.establishForMyUid(testLinkProperties);
+ assertUidRangesUpdatedForMyUid(true);
// Test that the VPN network returns a proxy, and the WiFi does not.
assertEquals(testProxyInfo, mService.getProxyForNetwork(mMockVpn.getNetwork()));
@@ -7378,6 +7450,7 @@
// The uid range needs to cover the test app so the network is visible to it.
final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
mMockVpn.establish(lp, VPN_UID, vpnRange);
+ assertVpnUidRangesUpdated(true, vpnRange, VPN_UID);
// A connected VPN should have interface rules set up. There are two expected invocations,
// one during the VPN initial connection, one during the VPN LinkProperties update.
@@ -7405,6 +7478,7 @@
// The uid range needs to cover the test app so the network is visible to it.
final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
mMockVpn.establish(lp, Process.SYSTEM_UID, vpnRange);
+ assertVpnUidRangesUpdated(true, vpnRange, Process.SYSTEM_UID);
// Legacy VPN should not have interface rules set up
verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any());
@@ -7420,6 +7494,7 @@
// The uid range needs to cover the test app so the network is visible to it.
final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
mMockVpn.establish(lp, Process.SYSTEM_UID, vpnRange);
+ assertVpnUidRangesUpdated(true, vpnRange, Process.SYSTEM_UID);
// IPv6 unreachable route should not be misinterpreted as a default route
verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any());
@@ -7434,6 +7509,7 @@
// The uid range needs to cover the test app so the network is visible to it.
final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
mMockVpn.establish(lp, VPN_UID, vpnRange);
+ assertVpnUidRangesUpdated(true, vpnRange, VPN_UID);
// Connected VPN should have interface rules set up. There are two expected invocations,
// one during VPN uid update, one during VPN LinkProperties update
@@ -7484,7 +7560,9 @@
lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null));
// The uid range needs to cover the test app so the network is visible to it.
final UidRange vpnRange = UidRange.createForUser(VPN_USER);
- mMockVpn.establish(lp, VPN_UID, Collections.singleton(vpnRange));
+ final Set<UidRange> vpnRanges = Collections.singleton(vpnRange);
+ mMockVpn.establish(lp, VPN_UID, vpnRanges);
+ assertVpnUidRangesUpdated(true, vpnRanges, VPN_UID);
reset(mMockNetd);
InOrder inOrder = inOrder(mMockNetd);
@@ -7635,6 +7713,7 @@
throws Exception {
final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
mMockVpn.establish(new LinkProperties(), vpnOwnerUid, vpnRange);
+ assertVpnUidRangesUpdated(true, vpnRange, vpnOwnerUid);
mMockVpn.setVpnType(vpnType);
final VpnInfo vpnInfo = new VpnInfo();
@@ -7892,6 +7971,7 @@
Manifest.permission.ACCESS_FINE_LOCATION);
mMockVpn.establishForMyUid();
+ assertUidRangesUpdatedForMyUid(true);
// Wait for networks to connect and broadcasts to be sent before removing permissions.
waitForIdle();
@@ -8171,4 +8251,54 @@
assertTrue(isRequestIdInOrder);
}
}
+
+ private void assertUidRangesUpdatedForMyUid(boolean add) throws Exception {
+ final int uid = Process.myUid();
+ assertVpnUidRangesUpdated(add, uidRangesForUid(uid), uid);
+ }
+
+ private void assertVpnUidRangesUpdated(boolean add, Set<UidRange> vpnRanges, int exemptUid)
+ throws Exception {
+ InOrder inOrder = inOrder(mMockNetd);
+ ArgumentCaptor<int[]> exemptUidCaptor = ArgumentCaptor.forClass(int[].class);
+
+ inOrder.verify(mMockNetd, times(1)).socketDestroy(eq(toUidRangeStableParcels(vpnRanges)),
+ exemptUidCaptor.capture());
+ assertContainsExactly(exemptUidCaptor.getValue(), Process.VPN_UID, exemptUid);
+
+ if (add) {
+ inOrder.verify(mMockNetd, times(1)).networkAddUidRanges(eq(mMockVpn.getNetId()),
+ eq(toUidRangeStableParcels(vpnRanges)));
+ } else {
+ inOrder.verify(mMockNetd, times(1)).networkRemoveUidRanges(eq(mMockVpn.getNetId()),
+ eq(toUidRangeStableParcels(vpnRanges)));
+ }
+
+ inOrder.verify(mMockNetd, times(1)).socketDestroy(eq(toUidRangeStableParcels(vpnRanges)),
+ exemptUidCaptor.capture());
+ assertContainsExactly(exemptUidCaptor.getValue(), Process.VPN_UID, exemptUid);
+ }
+
+ @Test
+ public void testVpnUidRangesUpdate() throws Exception {
+ LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName("tun0");
+ lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null));
+ lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null));
+ final UidRange vpnRange = UidRange.createForUser(VPN_USER);
+ Set<UidRange> vpnRanges = Collections.singleton(vpnRange);
+ mMockVpn.establish(lp, VPN_UID, vpnRanges);
+ assertVpnUidRangesUpdated(true, vpnRanges, VPN_UID);
+
+ reset(mMockNetd);
+ // Update to new range which is old range minus APP1, i.e. only APP2
+ final Set<UidRange> newRanges = new HashSet<>(Arrays.asList(
+ new UidRange(vpnRange.start, APP1_UID - 1),
+ new UidRange(APP1_UID + 1, vpnRange.stop)));
+ mMockVpn.setUids(newRanges);
+ waitForIdle();
+
+ assertVpnUidRangesUpdated(true, newRanges, VPN_UID);
+ assertVpnUidRangesUpdated(false, vpnRanges, VPN_UID);
+ }
}
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index 3648c4d..02a2aad 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -339,14 +339,8 @@
final Vpn vpn = createVpn(primaryUser.id);
final UidRange user = PRI_USER_RANGE;
- // Default state.
- assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1],
- user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
-
// Set always-on without lockdown.
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null, mKeyStore));
- assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1],
- user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
// Set always-on with lockdown.
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null, mKeyStore));
@@ -355,10 +349,6 @@
new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.stop)
}));
- assertBlocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[2],
- user.start + PKG_UIDS[3]);
- assertUnblocked(vpn, user.start + PKG_UIDS[1]);
-
// Switch to another app.
assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null, mKeyStore));
verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
@@ -369,9 +359,6 @@
new UidRangeParcel(user.start, user.start + PKG_UIDS[3] - 1),
new UidRangeParcel(user.start + PKG_UIDS[3] + 1, user.stop)
}));
- assertBlocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1],
- user.start + PKG_UIDS[2]);
- assertUnblocked(vpn, user.start + PKG_UIDS[3]);
}
@Test
@@ -386,8 +373,6 @@
new UidRangeParcel(user.start, user.start + PKG_UIDS[1] - 1),
new UidRangeParcel(user.start + PKG_UIDS[2] + 1, user.stop)
}));
- assertBlocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[3]);
- assertUnblocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]);
// Change allowed app list to PKGS[3].
assertTrue(vpn.setAlwaysOnPackage(
PKGS[1], true, Collections.singletonList(PKGS[3]), mKeyStore));
@@ -398,8 +383,6 @@
new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.start + PKG_UIDS[3] - 1),
new UidRangeParcel(user.start + PKG_UIDS[3] + 1, user.stop)
}));
- assertBlocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[2]);
- assertUnblocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[3]);
// Change the VPN app.
assertTrue(vpn.setAlwaysOnPackage(
@@ -412,8 +395,6 @@
new UidRangeParcel(user.start, user.start + PKG_UIDS[0] - 1),
new UidRangeParcel(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[3] - 1)
}));
- assertBlocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]);
- assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[3]);
// Remove the list of allowed packages.
assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null, mKeyStore));
@@ -424,9 +405,6 @@
verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
new UidRangeParcel(user.start + PKG_UIDS[0] + 1, user.stop),
}));
- assertBlocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2],
- user.start + PKG_UIDS[3]);
- assertUnblocked(vpn, user.start + PKG_UIDS[0]);
// Add the list of allowed packages.
assertTrue(vpn.setAlwaysOnPackage(
@@ -438,8 +416,6 @@
new UidRangeParcel(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[1] - 1),
new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.stop)
}));
- assertBlocked(vpn, user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
- assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1]);
// Try allowing a package with a comma, should be rejected.
assertFalse(vpn.setAlwaysOnPackage(
@@ -460,45 +436,6 @@
}
@Test
- public void testLockdownAddingAProfile() throws Exception {
- final Vpn vpn = createVpn(primaryUser.id);
- setMockedUsers(primaryUser);
-
- // Make a copy of the restricted profile, as we're going to mark it deleted halfway through.
- final UserInfo tempProfile = new UserInfo(restrictedProfileA.id, restrictedProfileA.name,
- restrictedProfileA.flags);
- tempProfile.restrictedProfileParentId = primaryUser.id;
-
- final UidRange user = PRI_USER_RANGE;
- final UidRange profile = UidRange.createForUser(tempProfile.id);
-
- // Set lockdown.
- assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null, mKeyStore));
- verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
- new UidRangeParcel(user.start, user.start + PKG_UIDS[3] - 1),
- new UidRangeParcel(user.start + PKG_UIDS[3] + 1, user.stop)
- }));
- // Verify restricted user isn't affected at first.
- assertUnblocked(vpn, profile.start + PKG_UIDS[0]);
-
- // Add the restricted user.
- setMockedUsers(primaryUser, tempProfile);
- vpn.onUserAdded(tempProfile.id);
- verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
- new UidRangeParcel(profile.start, profile.start + PKG_UIDS[3] - 1),
- new UidRangeParcel(profile.start + PKG_UIDS[3] + 1, profile.stop)
- }));
-
- // Remove the restricted user.
- tempProfile.partial = true;
- vpn.onUserRemoved(tempProfile.id);
- verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
- new UidRangeParcel(profile.start, profile.start + PKG_UIDS[3] - 1),
- new UidRangeParcel(profile.start + PKG_UIDS[3] + 1, profile.stop)
- }));
- }
-
- @Test
public void testLockdownRuleRepeatability() throws Exception {
final Vpn vpn = createVpn(primaryUser.id);
final UidRangeParcel[] primaryUserRangeParcel = new UidRangeParcel[] {
@@ -1207,20 +1144,6 @@
return vpn;
}
- private static void assertBlocked(Vpn vpn, int... uids) {
- for (int uid : uids) {
- final boolean blocked = vpn.getLockdown() && vpn.isBlockingUid(uid);
- assertTrue("Uid " + uid + " should be blocked", blocked);
- }
- }
-
- private static void assertUnblocked(Vpn vpn, int... uids) {
- for (int uid : uids) {
- final boolean blocked = vpn.getLockdown() && vpn.isBlockingUid(uid);
- assertFalse("Uid " + uid + " should not be blocked", blocked);
- }
- }
-
/**
* Populate {@link #mUserManager} with a list of fake users.
*/